mirror of https://github.com/usbharu/Hideout.git
Merge pull request #205 from usbharu/feature/refactor-user-relationship
Feature/refactor user relationship
This commit is contained in:
commit
32cd7ad1db
|
@ -287,7 +287,7 @@ project.gradle.taskGraph.whenReady {
|
||||||
kover {
|
kover {
|
||||||
|
|
||||||
excludeSourceSets {
|
excludeSourceSets {
|
||||||
names("aot")
|
names("aot", "e2eTest", "intTest")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,8 +21,9 @@ VALUES (9, 'test-user9', 'follower.example.com', 'Im test-user9.', 'THis account
|
||||||
'https://follower.example.com/users/test-user9/following',
|
'https://follower.example.com/users/test-user9/following',
|
||||||
'https://follower.example.com/users/test-user9/followers', null);
|
'https://follower.example.com/users/test-user9/followers', null);
|
||||||
|
|
||||||
insert into USERS_FOLLOWERS (USER_ID, FOLLOWER_ID)
|
insert into relationships (user_id, target_user_id, following, blocking, muting, follow_request,
|
||||||
VALUES (8, 9);
|
ignore_follow_request)
|
||||||
|
VALUES (9, 8, true, false, false, false, false);
|
||||||
|
|
||||||
insert into POSTS (ID, USER_ID, OVERVIEW, TEXT, CREATED_AT, VISIBILITY, URL, REPLY_ID, REPOST_ID, SENSITIVE, AP_ID)
|
insert into POSTS (ID, USER_ID, OVERVIEW, TEXT, CREATED_AT, VISIBILITY, URL, REPLY_ID, REPOST_ID, SENSITIVE, AP_ID)
|
||||||
VALUES (1239, 8, null, 'test post', 12345680, 2, 'https://example.com/users/test-user8/posts/1239', null, null, false,
|
VALUES (1239, 8, null, 'test post', 12345680, 2, 'https://example.com/users/test-user8/posts/1239', null, null, false,
|
||||||
|
|
|
@ -21,8 +21,9 @@ VALUES (5, 'test-user5', 'follower.example.com', 'Im test user5.', 'THis account
|
||||||
'https://follower.example.com/users/test-user5/following',
|
'https://follower.example.com/users/test-user5/following',
|
||||||
'https://follower.example.com/users/test-user5/followers', null);
|
'https://follower.example.com/users/test-user5/followers', null);
|
||||||
|
|
||||||
insert into USERS_FOLLOWERS (USER_ID, FOLLOWER_ID)
|
insert into relationships (user_id, target_user_id, following, blocking, muting, follow_request,
|
||||||
VALUES (4, 5);
|
ignore_follow_request)
|
||||||
|
VALUES (5, 4, true, false, false, false, false);
|
||||||
|
|
||||||
insert into POSTS (ID, "USER_ID", OVERVIEW, TEXT, "CREATED_AT", VISIBILITY, URL, "REPOST_ID", "REPLY_ID", SENSITIVE,
|
insert into POSTS (ID, "USER_ID", OVERVIEW, TEXT, "CREATED_AT", VISIBILITY, URL, "REPOST_ID", "REPLY_ID", SENSITIVE,
|
||||||
AP_ID)
|
AP_ID)
|
||||||
|
|
|
@ -21,8 +21,9 @@ VALUES (7, 'test-user7', 'follower.example.com', 'Im test-user7.', 'THis account
|
||||||
'https://follower.example.com/users/test-user7/following',
|
'https://follower.example.com/users/test-user7/following',
|
||||||
'https://follower.example.com/users/test-user7/followers', null);
|
'https://follower.example.com/users/test-user7/followers', null);
|
||||||
|
|
||||||
insert into USERS_FOLLOWERS (USER_ID, FOLLOWER_ID)
|
insert into relationships (user_id, target_user_id, following, blocking, muting, follow_request,
|
||||||
VALUES (6, 7);
|
ignore_follow_request)
|
||||||
|
VALUES (7, 6, true, false, false, false, false);
|
||||||
|
|
||||||
insert into POSTS (ID, "USER_ID", OVERVIEW, TEXT, "CREATED_AT", VISIBILITY, URL, "REPOST_ID", "REPLY_ID", SENSITIVE,
|
insert into POSTS (ID, "USER_ID", OVERVIEW, TEXT, "CREATED_AT", VISIBILITY, URL, "REPOST_ID", "REPLY_ID", SENSITIVE,
|
||||||
AP_ID)
|
AP_ID)
|
||||||
|
|
|
@ -8,7 +8,6 @@ import dev.usbharu.hideout.activitypub.domain.model.objects.ObjectDeserializer
|
||||||
|
|
||||||
open class Accept @JsonCreator constructor(
|
open class Accept @JsonCreator constructor(
|
||||||
type: List<String> = emptyList(),
|
type: List<String> = emptyList(),
|
||||||
override val name: String,
|
|
||||||
@JsonDeserialize(using = ObjectDeserializer::class)
|
@JsonDeserialize(using = ObjectDeserializer::class)
|
||||||
@JsonProperty("object")
|
@JsonProperty("object")
|
||||||
val apObject: Object,
|
val apObject: Object,
|
||||||
|
@ -16,9 +15,7 @@ open class Accept @JsonCreator constructor(
|
||||||
) : Object(
|
) : Object(
|
||||||
type = add(type, "Accept")
|
type = add(type, "Accept")
|
||||||
),
|
),
|
||||||
HasActor,
|
HasActor {
|
||||||
HasName {
|
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
override fun equals(other: Any?): Boolean {
|
||||||
if (this === other) return true
|
if (this === other) return true
|
||||||
if (javaClass != other?.javaClass) return false
|
if (javaClass != other?.javaClass) return false
|
||||||
|
@ -26,7 +23,6 @@ open class Accept @JsonCreator constructor(
|
||||||
|
|
||||||
other as Accept
|
other as Accept
|
||||||
|
|
||||||
if (name != other.name) return false
|
|
||||||
if (apObject != other.apObject) return false
|
if (apObject != other.apObject) return false
|
||||||
if (actor != other.actor) return false
|
if (actor != other.actor) return false
|
||||||
|
|
||||||
|
@ -35,7 +31,6 @@ open class Accept @JsonCreator constructor(
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
override fun hashCode(): Int {
|
||||||
var result = super.hashCode()
|
var result = super.hashCode()
|
||||||
result = 31 * result + name.hashCode()
|
|
||||||
result = 31 * result + apObject.hashCode()
|
result = 31 * result + apObject.hashCode()
|
||||||
result = 31 * result + actor.hashCode()
|
result = 31 * result + actor.hashCode()
|
||||||
return result
|
return result
|
||||||
|
@ -43,7 +38,6 @@ open class Accept @JsonCreator constructor(
|
||||||
|
|
||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
return "Accept(" +
|
return "Accept(" +
|
||||||
"name='$name', " +
|
|
||||||
"apObject=$apObject, " +
|
"apObject=$apObject, " +
|
||||||
"actor='$actor'" +
|
"actor='$actor'" +
|
||||||
")" +
|
")" +
|
||||||
|
|
|
@ -0,0 +1,43 @@
|
||||||
|
package dev.usbharu.hideout.activitypub.domain.model
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty
|
||||||
|
import dev.usbharu.hideout.activitypub.domain.model.objects.Object
|
||||||
|
|
||||||
|
open class Block(
|
||||||
|
override val actor: String,
|
||||||
|
override val id: String,
|
||||||
|
@JsonProperty("object") val apObject: String
|
||||||
|
) :
|
||||||
|
Object(listOf("Block")), HasId, HasActor {
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (this === other) return true
|
||||||
|
if (javaClass != other?.javaClass) return false
|
||||||
|
if (!super.equals(other)) return false
|
||||||
|
|
||||||
|
other as Block
|
||||||
|
|
||||||
|
if (actor != other.actor) return false
|
||||||
|
if (id != other.id) return false
|
||||||
|
if (apObject != other.apObject) return false
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
var result = super.hashCode()
|
||||||
|
result = 31 * result + actor.hashCode()
|
||||||
|
result = 31 * result + id.hashCode()
|
||||||
|
result = 31 * result + apObject.hashCode()
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return "Block(" +
|
||||||
|
"actor='$actor', " +
|
||||||
|
"id='$id', " +
|
||||||
|
"apObject='$apObject'" +
|
||||||
|
")" +
|
||||||
|
" ${super.toString()}"
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,7 +7,7 @@ import dev.usbharu.hideout.activitypub.domain.model.objects.ObjectDeserializer
|
||||||
|
|
||||||
open class Create(
|
open class Create(
|
||||||
type: List<String> = emptyList(),
|
type: List<String> = emptyList(),
|
||||||
override val name: String,
|
val name: String? = null,
|
||||||
@JsonDeserialize(using = ObjectDeserializer::class)
|
@JsonDeserialize(using = ObjectDeserializer::class)
|
||||||
@JsonProperty("object")
|
@JsonProperty("object")
|
||||||
val apObject: Object,
|
val apObject: Object,
|
||||||
|
@ -19,7 +19,6 @@ open class Create(
|
||||||
type = add(type, "Create")
|
type = add(type, "Create")
|
||||||
),
|
),
|
||||||
HasId,
|
HasId,
|
||||||
HasName,
|
|
||||||
HasActor {
|
HasActor {
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
override fun equals(other: Any?): Boolean {
|
||||||
|
@ -41,7 +40,7 @@ open class Create(
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
override fun hashCode(): Int {
|
||||||
var result = super.hashCode()
|
var result = super.hashCode()
|
||||||
result = 31 * result + name.hashCode()
|
result = 31 * result + (name?.hashCode() ?: 0)
|
||||||
result = 31 * result + apObject.hashCode()
|
result = 31 * result + apObject.hashCode()
|
||||||
result = 31 * result + actor.hashCode()
|
result = 31 * result + actor.hashCode()
|
||||||
result = 31 * result + id.hashCode()
|
result = 31 * result + id.hashCode()
|
||||||
|
@ -52,7 +51,7 @@ open class Create(
|
||||||
|
|
||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
return "Create(" +
|
return "Create(" +
|
||||||
"name='$name', " +
|
"name=$name, " +
|
||||||
"apObject=$apObject, " +
|
"apObject=$apObject, " +
|
||||||
"actor='$actor', " +
|
"actor='$actor', " +
|
||||||
"id='$id', " +
|
"id='$id', " +
|
||||||
|
|
|
@ -0,0 +1,43 @@
|
||||||
|
package dev.usbharu.hideout.activitypub.domain.model
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty
|
||||||
|
import com.fasterxml.jackson.databind.annotation.JsonDeserialize
|
||||||
|
import dev.usbharu.hideout.activitypub.domain.model.objects.Object
|
||||||
|
import dev.usbharu.hideout.activitypub.domain.model.objects.ObjectDeserializer
|
||||||
|
|
||||||
|
open class Reject(
|
||||||
|
override val actor: String,
|
||||||
|
override val id: String,
|
||||||
|
@JsonDeserialize(using = ObjectDeserializer::class) @JsonProperty("object") val apObject: Object
|
||||||
|
) : Object(listOf("Reject")), HasId, HasActor {
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (this === other) return true
|
||||||
|
if (javaClass != other?.javaClass) return false
|
||||||
|
if (!super.equals(other)) return false
|
||||||
|
|
||||||
|
other as Reject
|
||||||
|
|
||||||
|
if (actor != other.actor) return false
|
||||||
|
if (id != other.id) return false
|
||||||
|
if (apObject != other.apObject) return false
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
var result = super.hashCode()
|
||||||
|
result = 31 * result + actor.hashCode()
|
||||||
|
result = 31 * result + id.hashCode()
|
||||||
|
result = 31 * result + apObject.hashCode()
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return "Reject(" +
|
||||||
|
"actor='$actor', " +
|
||||||
|
"id='$id', " +
|
||||||
|
"apObject=$apObject" +
|
||||||
|
")" +
|
||||||
|
" ${super.toString()}"
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,6 @@
|
||||||
package dev.usbharu.hideout.activitypub.domain.model
|
package dev.usbharu.hideout.activitypub.domain.model
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty
|
||||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize
|
import com.fasterxml.jackson.databind.annotation.JsonDeserialize
|
||||||
import dev.usbharu.hideout.activitypub.domain.model.objects.Object
|
import dev.usbharu.hideout.activitypub.domain.model.objects.Object
|
||||||
import dev.usbharu.hideout.activitypub.domain.model.objects.ObjectDeserializer
|
import dev.usbharu.hideout.activitypub.domain.model.objects.ObjectDeserializer
|
||||||
|
@ -9,7 +10,7 @@ open class Undo(
|
||||||
override val actor: String,
|
override val actor: String,
|
||||||
override val id: String,
|
override val id: String,
|
||||||
@JsonDeserialize(using = ObjectDeserializer::class)
|
@JsonDeserialize(using = ObjectDeserializer::class)
|
||||||
@Suppress("VariableNaming") val `object`: Object,
|
@JsonProperty("object") val apObject: Object,
|
||||||
val published: String
|
val published: String
|
||||||
) : Object(add(type, "Undo")), HasId, HasActor {
|
) : Object(add(type, "Undo")), HasId, HasActor {
|
||||||
|
|
||||||
|
@ -20,7 +21,7 @@ open class Undo(
|
||||||
|
|
||||||
other as Undo
|
other as Undo
|
||||||
|
|
||||||
if (`object` != other.`object`) return false
|
if (apObject != other.apObject) return false
|
||||||
if (published != other.published) return false
|
if (published != other.published) return false
|
||||||
if (actor != other.actor) return false
|
if (actor != other.actor) return false
|
||||||
if (id != other.id) return false
|
if (id != other.id) return false
|
||||||
|
@ -30,7 +31,7 @@ open class Undo(
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
override fun hashCode(): Int {
|
||||||
var result = super.hashCode()
|
var result = super.hashCode()
|
||||||
result = 31 * result + `object`.hashCode()
|
result = 31 * result + apObject.hashCode()
|
||||||
result = 31 * result + published.hashCode()
|
result = 31 * result + published.hashCode()
|
||||||
result = 31 * result + actor.hashCode()
|
result = 31 * result + actor.hashCode()
|
||||||
result = 31 * result + id.hashCode()
|
result = 31 * result + id.hashCode()
|
||||||
|
@ -38,5 +39,5 @@ open class Undo(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun toString(): String =
|
override fun toString(): String =
|
||||||
"Undo(`object`=$`object`, published=$published, actor='$actor', id='$id') ${super.toString()}"
|
"Undo(`object`=$apObject, published=$published, actor='$actor', id='$id') ${super.toString()}"
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,7 +44,7 @@ class ObjectDeserializer : JsonDeserializer<Object>() {
|
||||||
ExtendedActivityVocabulary.Add -> TODO()
|
ExtendedActivityVocabulary.Add -> TODO()
|
||||||
ExtendedActivityVocabulary.Announce -> TODO()
|
ExtendedActivityVocabulary.Announce -> TODO()
|
||||||
ExtendedActivityVocabulary.Arrive -> TODO()
|
ExtendedActivityVocabulary.Arrive -> TODO()
|
||||||
ExtendedActivityVocabulary.Block -> TODO()
|
ExtendedActivityVocabulary.Block -> p.codec.treeToValue(treeNode, Block::class.java)
|
||||||
ExtendedActivityVocabulary.Create -> p.codec.treeToValue(treeNode, Create::class.java)
|
ExtendedActivityVocabulary.Create -> p.codec.treeToValue(treeNode, Create::class.java)
|
||||||
ExtendedActivityVocabulary.Delete -> p.codec.treeToValue(treeNode, Delete::class.java)
|
ExtendedActivityVocabulary.Delete -> p.codec.treeToValue(treeNode, Delete::class.java)
|
||||||
ExtendedActivityVocabulary.Dislike -> TODO()
|
ExtendedActivityVocabulary.Dislike -> TODO()
|
||||||
|
@ -58,7 +58,7 @@ class ObjectDeserializer : JsonDeserializer<Object>() {
|
||||||
ExtendedActivityVocabulary.Move -> TODO()
|
ExtendedActivityVocabulary.Move -> TODO()
|
||||||
ExtendedActivityVocabulary.Offer -> TODO()
|
ExtendedActivityVocabulary.Offer -> TODO()
|
||||||
ExtendedActivityVocabulary.Question -> TODO()
|
ExtendedActivityVocabulary.Question -> TODO()
|
||||||
ExtendedActivityVocabulary.Reject -> TODO()
|
ExtendedActivityVocabulary.Reject -> p.codec.treeToValue(treeNode, Reject::class.java)
|
||||||
ExtendedActivityVocabulary.Read -> TODO()
|
ExtendedActivityVocabulary.Read -> TODO()
|
||||||
ExtendedActivityVocabulary.Remove -> TODO()
|
ExtendedActivityVocabulary.Remove -> TODO()
|
||||||
ExtendedActivityVocabulary.TentativeReject -> TODO()
|
ExtendedActivityVocabulary.TentativeReject -> TODO()
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
package dev.usbharu.hideout.activitypub.service.activity.accept
|
||||||
|
|
||||||
|
import dev.usbharu.hideout.activitypub.service.common.APRequestService
|
||||||
|
import dev.usbharu.hideout.application.external.Transaction
|
||||||
|
import dev.usbharu.hideout.core.external.job.DeliverAcceptJob
|
||||||
|
import dev.usbharu.hideout.core.external.job.DeliverAcceptJobParam
|
||||||
|
import dev.usbharu.hideout.core.query.UserQueryService
|
||||||
|
import dev.usbharu.hideout.core.service.job.JobProcessor
|
||||||
|
import org.springframework.stereotype.Service
|
||||||
|
|
||||||
|
@Service
|
||||||
|
class APDeliverAcceptJobProcessor(
|
||||||
|
private val apRequestService: APRequestService,
|
||||||
|
private val userQueryService: UserQueryService,
|
||||||
|
private val deliverAcceptJob: DeliverAcceptJob,
|
||||||
|
private val transaction: Transaction
|
||||||
|
) :
|
||||||
|
JobProcessor<DeliverAcceptJobParam, DeliverAcceptJob> {
|
||||||
|
override suspend fun process(param: DeliverAcceptJobParam): Unit = transaction.transaction {
|
||||||
|
apRequestService.apPost(param.inbox, param.accept, userQueryService.findById(param.signer))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun job(): DeliverAcceptJob = deliverAcceptJob
|
||||||
|
}
|
|
@ -7,22 +7,20 @@ import dev.usbharu.hideout.activitypub.service.common.AbstractActivityPubProcess
|
||||||
import dev.usbharu.hideout.activitypub.service.common.ActivityPubProcessContext
|
import dev.usbharu.hideout.activitypub.service.common.ActivityPubProcessContext
|
||||||
import dev.usbharu.hideout.activitypub.service.common.ActivityType
|
import dev.usbharu.hideout.activitypub.service.common.ActivityType
|
||||||
import dev.usbharu.hideout.application.external.Transaction
|
import dev.usbharu.hideout.application.external.Transaction
|
||||||
import dev.usbharu.hideout.core.query.FollowerQueryService
|
|
||||||
import dev.usbharu.hideout.core.query.UserQueryService
|
import dev.usbharu.hideout.core.query.UserQueryService
|
||||||
import dev.usbharu.hideout.core.service.user.UserService
|
import dev.usbharu.hideout.core.service.relationship.RelationshipService
|
||||||
import org.springframework.stereotype.Service
|
import org.springframework.stereotype.Service
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
class ApAcceptProcessor(
|
class ApAcceptProcessor(
|
||||||
transaction: Transaction,
|
transaction: Transaction,
|
||||||
private val userQueryService: UserQueryService,
|
private val userQueryService: UserQueryService,
|
||||||
private val followerQueryService: FollowerQueryService,
|
private val relationshipService: RelationshipService
|
||||||
private val userService: UserService
|
|
||||||
) :
|
) :
|
||||||
AbstractActivityPubProcessor<Accept>(transaction) {
|
AbstractActivityPubProcessor<Accept>(transaction) {
|
||||||
|
|
||||||
override suspend fun internalProcess(activity: ActivityPubProcessContext<Accept>) {
|
override suspend fun internalProcess(activity: ActivityPubProcessContext<Accept>) {
|
||||||
val value = activity.activity.apObject ?: throw IllegalActivityPubObjectException("object is null")
|
val value = activity.activity.apObject
|
||||||
|
|
||||||
if (value.type.contains("Follow").not()) {
|
if (value.type.contains("Follow").not()) {
|
||||||
logger.warn("FAILED Activity type isn't Follow.")
|
logger.warn("FAILED Activity type isn't Follow.")
|
||||||
|
@ -37,13 +35,8 @@ class ApAcceptProcessor(
|
||||||
val user = userQueryService.findByUrl(userUrl)
|
val user = userQueryService.findByUrl(userUrl)
|
||||||
val follower = userQueryService.findByUrl(followerUrl)
|
val follower = userQueryService.findByUrl(followerUrl)
|
||||||
|
|
||||||
if (followerQueryService.alreadyFollow(user.id, follower.id)) {
|
relationshipService.acceptFollowRequest(user.id, follower.id)
|
||||||
logger.debug("END User already follow from ${follower.url} to ${user.url}.")
|
logger.debug("SUCCESS Follow from ${user.url} to ${follower.url}.")
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
userService.follow(user.id, follower.id)
|
|
||||||
logger.debug("SUCCESS Follow from ${follower.url} to ${user.url}.")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun isSupported(activityType: ActivityType): Boolean = activityType == ActivityType.Accept
|
override fun isSupported(activityType: ActivityType): Boolean = activityType == ActivityType.Accept
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
package dev.usbharu.hideout.activitypub.service.activity.accept
|
||||||
|
|
||||||
|
import dev.usbharu.hideout.activitypub.domain.model.Accept
|
||||||
|
import dev.usbharu.hideout.activitypub.domain.model.Follow
|
||||||
|
import dev.usbharu.hideout.core.domain.model.user.User
|
||||||
|
import dev.usbharu.hideout.core.external.job.DeliverAcceptJob
|
||||||
|
import dev.usbharu.hideout.core.external.job.DeliverAcceptJobParam
|
||||||
|
import dev.usbharu.hideout.core.service.job.JobQueueParentService
|
||||||
|
import org.springframework.stereotype.Service
|
||||||
|
|
||||||
|
interface ApSendAcceptService {
|
||||||
|
suspend fun sendAcceptFollow(user: User, target: User)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Service
|
||||||
|
class ApSendAcceptServiceImpl(
|
||||||
|
private val jobQueueParentService: JobQueueParentService,
|
||||||
|
private val deliverAcceptJob: DeliverAcceptJob
|
||||||
|
) : ApSendAcceptService {
|
||||||
|
override suspend fun sendAcceptFollow(user: User, target: User) {
|
||||||
|
val deliverAcceptJobParam = DeliverAcceptJobParam(
|
||||||
|
Accept(
|
||||||
|
apObject = Follow(
|
||||||
|
apObject = user.url,
|
||||||
|
actor = target.url
|
||||||
|
),
|
||||||
|
actor = user.url
|
||||||
|
),
|
||||||
|
target.inbox,
|
||||||
|
user.id
|
||||||
|
)
|
||||||
|
|
||||||
|
jobQueueParentService.scheduleTypeSafe(deliverAcceptJob, deliverAcceptJobParam)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
package dev.usbharu.hideout.activitypub.service.activity.block
|
||||||
|
|
||||||
|
import dev.usbharu.hideout.activitypub.service.common.APRequestService
|
||||||
|
import dev.usbharu.hideout.application.external.Transaction
|
||||||
|
import dev.usbharu.hideout.core.domain.model.user.UserRepository
|
||||||
|
import dev.usbharu.hideout.core.external.job.DeliverBlockJob
|
||||||
|
import dev.usbharu.hideout.core.external.job.DeliverBlockJobParam
|
||||||
|
import dev.usbharu.hideout.core.service.job.JobProcessor
|
||||||
|
import org.springframework.stereotype.Service
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ブロックアクティビティ配送を処理します
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
class APDeliverBlockJobProcessor(
|
||||||
|
private val apRequestService: APRequestService,
|
||||||
|
private val userRepository: UserRepository,
|
||||||
|
private val transaction: Transaction,
|
||||||
|
private val deliverBlockJob: DeliverBlockJob
|
||||||
|
) : JobProcessor<DeliverBlockJobParam, DeliverBlockJob> {
|
||||||
|
override suspend fun process(param: DeliverBlockJobParam): Unit = transaction.transaction {
|
||||||
|
val signer = userRepository.findById(param.signer)
|
||||||
|
apRequestService.apPost(
|
||||||
|
param.inbox,
|
||||||
|
param.reject,
|
||||||
|
signer
|
||||||
|
)
|
||||||
|
apRequestService.apPost(
|
||||||
|
param.inbox,
|
||||||
|
param.block,
|
||||||
|
signer
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun job(): DeliverBlockJob = deliverBlockJob
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
package dev.usbharu.hideout.activitypub.service.activity.block
|
||||||
|
|
||||||
|
import dev.usbharu.hideout.activitypub.domain.model.Block
|
||||||
|
import dev.usbharu.hideout.activitypub.domain.model.Follow
|
||||||
|
import dev.usbharu.hideout.activitypub.domain.model.Reject
|
||||||
|
import dev.usbharu.hideout.application.config.ApplicationConfig
|
||||||
|
import dev.usbharu.hideout.core.domain.model.user.User
|
||||||
|
import dev.usbharu.hideout.core.external.job.DeliverBlockJob
|
||||||
|
import dev.usbharu.hideout.core.external.job.DeliverBlockJobParam
|
||||||
|
import dev.usbharu.hideout.core.service.job.JobQueueParentService
|
||||||
|
import org.springframework.stereotype.Service
|
||||||
|
|
||||||
|
interface APSendBlockService {
|
||||||
|
suspend fun sendBlock(user: User, target: User)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Service
|
||||||
|
class ApSendBlockServiceImpl(
|
||||||
|
private val applicationConfig: ApplicationConfig,
|
||||||
|
private val jobQueueParentService: JobQueueParentService,
|
||||||
|
private val deliverBlockJob: DeliverBlockJob
|
||||||
|
) : APSendBlockService {
|
||||||
|
override suspend fun sendBlock(user: User, target: User) {
|
||||||
|
val blockJobParam = DeliverBlockJobParam(
|
||||||
|
user.id,
|
||||||
|
Block(
|
||||||
|
user.url,
|
||||||
|
"${applicationConfig.url}/block/${user.id}/${target.id}",
|
||||||
|
target.url
|
||||||
|
),
|
||||||
|
Reject(
|
||||||
|
user.url,
|
||||||
|
"${applicationConfig.url}/reject/${user.id}/${target.id}",
|
||||||
|
Follow(
|
||||||
|
apObject = user.url,
|
||||||
|
actor = target.url
|
||||||
|
)
|
||||||
|
),
|
||||||
|
target.inbox
|
||||||
|
)
|
||||||
|
jobQueueParentService.scheduleTypeSafe(deliverBlockJob, blockJobParam)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
package dev.usbharu.hideout.activitypub.service.activity.block
|
||||||
|
|
||||||
|
import dev.usbharu.hideout.activitypub.domain.model.Block
|
||||||
|
import dev.usbharu.hideout.activitypub.service.common.AbstractActivityPubProcessor
|
||||||
|
import dev.usbharu.hideout.activitypub.service.common.ActivityPubProcessContext
|
||||||
|
import dev.usbharu.hideout.activitypub.service.common.ActivityType
|
||||||
|
import dev.usbharu.hideout.application.external.Transaction
|
||||||
|
import dev.usbharu.hideout.core.query.UserQueryService
|
||||||
|
import dev.usbharu.hideout.core.service.relationship.RelationshipService
|
||||||
|
import org.springframework.stereotype.Service
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ブロックアクティビティを処理します
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
class BlockActivityPubProcessor(
|
||||||
|
private val userQueryService: UserQueryService,
|
||||||
|
private val relationshipService: RelationshipService,
|
||||||
|
transaction: Transaction
|
||||||
|
) :
|
||||||
|
AbstractActivityPubProcessor<Block>(transaction) {
|
||||||
|
override suspend fun internalProcess(activity: ActivityPubProcessContext<Block>) {
|
||||||
|
val user = userQueryService.findByUrl(activity.activity.actor)
|
||||||
|
val target = userQueryService.findByUrl(activity.activity.apObject)
|
||||||
|
relationshipService.block(user.id, target.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun isSupported(activityType: ActivityType): Boolean = activityType == ActivityType.Block
|
||||||
|
|
||||||
|
override fun type(): Class<Block> = Block::class.java
|
||||||
|
}
|
|
@ -2,16 +2,14 @@ package dev.usbharu.hideout.activitypub.service.activity.follow
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper
|
import com.fasterxml.jackson.databind.ObjectMapper
|
||||||
import com.fasterxml.jackson.module.kotlin.readValue
|
import com.fasterxml.jackson.module.kotlin.readValue
|
||||||
import dev.usbharu.hideout.activitypub.domain.model.Accept
|
|
||||||
import dev.usbharu.hideout.activitypub.domain.model.Follow
|
import dev.usbharu.hideout.activitypub.domain.model.Follow
|
||||||
import dev.usbharu.hideout.activitypub.service.common.APRequestService
|
|
||||||
import dev.usbharu.hideout.activitypub.service.objects.user.APUserService
|
import dev.usbharu.hideout.activitypub.service.objects.user.APUserService
|
||||||
import dev.usbharu.hideout.application.external.Transaction
|
import dev.usbharu.hideout.application.external.Transaction
|
||||||
import dev.usbharu.hideout.core.external.job.ReceiveFollowJob
|
import dev.usbharu.hideout.core.external.job.ReceiveFollowJob
|
||||||
import dev.usbharu.hideout.core.external.job.ReceiveFollowJobParam
|
import dev.usbharu.hideout.core.external.job.ReceiveFollowJobParam
|
||||||
import dev.usbharu.hideout.core.query.UserQueryService
|
import dev.usbharu.hideout.core.query.UserQueryService
|
||||||
import dev.usbharu.hideout.core.service.job.JobProcessor
|
import dev.usbharu.hideout.core.service.job.JobProcessor
|
||||||
import dev.usbharu.hideout.core.service.user.UserService
|
import dev.usbharu.hideout.core.service.relationship.RelationshipService
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
import org.springframework.stereotype.Service
|
import org.springframework.stereotype.Service
|
||||||
|
|
||||||
|
@ -21,35 +19,21 @@ class APReceiveFollowJobProcessor(
|
||||||
private val userQueryService: UserQueryService,
|
private val userQueryService: UserQueryService,
|
||||||
private val apUserService: APUserService,
|
private val apUserService: APUserService,
|
||||||
private val objectMapper: ObjectMapper,
|
private val objectMapper: ObjectMapper,
|
||||||
private val apRequestService: APRequestService,
|
private val relationshipService: RelationshipService
|
||||||
private val userService: UserService
|
|
||||||
) :
|
) :
|
||||||
JobProcessor<ReceiveFollowJobParam, ReceiveFollowJob> {
|
JobProcessor<ReceiveFollowJobParam, ReceiveFollowJob> {
|
||||||
override suspend fun process(param: ReceiveFollowJobParam) = transaction.transaction {
|
override suspend fun process(param: ReceiveFollowJobParam) = transaction.transaction {
|
||||||
val person = apUserService.fetchPerson(param.actor, param.targetActor)
|
apUserService.fetchPerson(param.actor, param.targetActor)
|
||||||
val follow = objectMapper.readValue<Follow>(param.follow)
|
val follow = objectMapper.readValue<Follow>(param.follow)
|
||||||
|
|
||||||
logger.info("START Follow from: {} to {}", param.targetActor, param.actor)
|
logger.info("START Follow from: {} to {}", param.targetActor, param.actor)
|
||||||
|
|
||||||
val signer = userQueryService.findByUrl(param.targetActor)
|
|
||||||
|
|
||||||
val urlString = person.inbox
|
|
||||||
|
|
||||||
apRequestService.apPost(
|
|
||||||
url = urlString,
|
|
||||||
body = Accept(
|
|
||||||
name = "Follow",
|
|
||||||
apObject = follow,
|
|
||||||
actor = param.targetActor
|
|
||||||
),
|
|
||||||
signer = signer
|
|
||||||
)
|
|
||||||
|
|
||||||
val targetEntity = userQueryService.findByUrl(param.targetActor)
|
val targetEntity = userQueryService.findByUrl(param.targetActor)
|
||||||
val followActorEntity =
|
val followActorEntity =
|
||||||
userQueryService.findByUrl(follow.actor)
|
userQueryService.findByUrl(follow.actor)
|
||||||
|
|
||||||
userService.followRequest(targetEntity.id, followActorEntity.id)
|
relationshipService.followRequest(followActorEntity.id, targetEntity.id)
|
||||||
|
|
||||||
logger.info("SUCCESS Follow from: {} to: {}", param.targetActor, param.actor)
|
logger.info("SUCCESS Follow from: {} to: {}", param.targetActor, param.actor)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -31,7 +31,7 @@ class ApRemoveReactionJobProcessor(
|
||||||
param.inbox,
|
param.inbox,
|
||||||
Undo(
|
Undo(
|
||||||
actor = param.actor,
|
actor = param.actor,
|
||||||
`object` = like,
|
apObject = like,
|
||||||
id = "${applicationConfig.url}/undo/like/${param.id}",
|
id = "${applicationConfig.url}/undo/like/${param.id}",
|
||||||
published = Instant.now().toString()
|
published = Instant.now().toString()
|
||||||
),
|
),
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
package dev.usbharu.hideout.activitypub.service.activity.reject
|
||||||
|
|
||||||
|
import dev.usbharu.hideout.activitypub.service.common.APRequestService
|
||||||
|
import dev.usbharu.hideout.application.external.Transaction
|
||||||
|
import dev.usbharu.hideout.core.external.job.DeliverRejectJob
|
||||||
|
import dev.usbharu.hideout.core.external.job.DeliverRejectJobParam
|
||||||
|
import dev.usbharu.hideout.core.query.UserQueryService
|
||||||
|
import dev.usbharu.hideout.core.service.job.JobProcessor
|
||||||
|
import org.springframework.stereotype.Component
|
||||||
|
|
||||||
|
@Component
|
||||||
|
class APDeliverRejectJobProcessor(
|
||||||
|
private val apRequestService: APRequestService,
|
||||||
|
private val userQueryService: UserQueryService,
|
||||||
|
private val deliverRejectJob: DeliverRejectJob,
|
||||||
|
private val transaction: Transaction
|
||||||
|
) :
|
||||||
|
JobProcessor<DeliverRejectJobParam, DeliverRejectJob> {
|
||||||
|
override suspend fun process(param: DeliverRejectJobParam): Unit = transaction.transaction {
|
||||||
|
apRequestService.apPost(param.inbox, param.reject, userQueryService.findById(param.signer))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun job(): DeliverRejectJob = deliverRejectJob
|
||||||
|
}
|
|
@ -0,0 +1,49 @@
|
||||||
|
package dev.usbharu.hideout.activitypub.service.activity.reject
|
||||||
|
|
||||||
|
import dev.usbharu.hideout.activitypub.domain.model.Follow
|
||||||
|
import dev.usbharu.hideout.activitypub.domain.model.Reject
|
||||||
|
import dev.usbharu.hideout.activitypub.service.common.AbstractActivityPubProcessor
|
||||||
|
import dev.usbharu.hideout.activitypub.service.common.ActivityPubProcessContext
|
||||||
|
import dev.usbharu.hideout.activitypub.service.common.ActivityType
|
||||||
|
import dev.usbharu.hideout.application.external.Transaction
|
||||||
|
import dev.usbharu.hideout.core.query.UserQueryService
|
||||||
|
import dev.usbharu.hideout.core.service.relationship.RelationshipService
|
||||||
|
import org.springframework.stereotype.Service
|
||||||
|
|
||||||
|
@Service
|
||||||
|
class ApRejectProcessor(
|
||||||
|
private val relationshipService: RelationshipService,
|
||||||
|
private val userQueryService: UserQueryService,
|
||||||
|
transaction: Transaction
|
||||||
|
) :
|
||||||
|
AbstractActivityPubProcessor<Reject>(transaction) {
|
||||||
|
override suspend fun internalProcess(activity: ActivityPubProcessContext<Reject>) {
|
||||||
|
val activityType = activity.activity.apObject.type.firstOrNull { it == "Follow" }
|
||||||
|
|
||||||
|
if (activityType == null) {
|
||||||
|
logger.warn("FAILED Process Reject Activity type: {}", activity.activity.apObject.type)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
when (activityType) {
|
||||||
|
"Follow" -> {
|
||||||
|
val user = userQueryService.findByUrl(activity.activity.actor)
|
||||||
|
|
||||||
|
activity.activity.apObject as Follow
|
||||||
|
|
||||||
|
val actor = activity.activity.apObject.actor
|
||||||
|
|
||||||
|
val target = userQueryService.findByUrl(actor)
|
||||||
|
|
||||||
|
logger.debug("REJECT Follow user {} target {}", user.url, target.url)
|
||||||
|
|
||||||
|
relationshipService.rejectFollowRequest(user.id, target.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun isSupported(activityType: ActivityType): Boolean = activityType == ActivityType.Reject
|
||||||
|
|
||||||
|
override fun type(): Class<Reject> = Reject::class.java
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
package dev.usbharu.hideout.activitypub.service.activity.reject
|
||||||
|
|
||||||
|
import dev.usbharu.hideout.core.domain.model.user.User
|
||||||
|
|
||||||
|
interface ApSendRejectService {
|
||||||
|
suspend fun sendRejectFollow(user: User, target: User)
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
package dev.usbharu.hideout.activitypub.service.activity.reject
|
||||||
|
|
||||||
|
import dev.usbharu.hideout.activitypub.domain.model.Follow
|
||||||
|
import dev.usbharu.hideout.activitypub.domain.model.Reject
|
||||||
|
import dev.usbharu.hideout.application.config.ApplicationConfig
|
||||||
|
import dev.usbharu.hideout.core.domain.model.user.User
|
||||||
|
import dev.usbharu.hideout.core.external.job.DeliverRejectJob
|
||||||
|
import dev.usbharu.hideout.core.external.job.DeliverRejectJobParam
|
||||||
|
import dev.usbharu.hideout.core.service.job.JobQueueParentService
|
||||||
|
import org.springframework.stereotype.Service
|
||||||
|
|
||||||
|
@Service
|
||||||
|
class ApSendRejectServiceImpl(
|
||||||
|
private val applicationConfig: ApplicationConfig,
|
||||||
|
private val jobQueueParentService: JobQueueParentService,
|
||||||
|
private val deliverRejectJob: DeliverRejectJob
|
||||||
|
) : ApSendRejectService {
|
||||||
|
override suspend fun sendRejectFollow(user: User, target: User) {
|
||||||
|
val deliverRejectJobParam = DeliverRejectJobParam(
|
||||||
|
Reject(
|
||||||
|
user.url,
|
||||||
|
"${applicationConfig.url}/reject/${user.id}/${target.id}",
|
||||||
|
Follow(apObject = user.url, actor = target.url)
|
||||||
|
),
|
||||||
|
target.inbox,
|
||||||
|
user.id
|
||||||
|
)
|
||||||
|
|
||||||
|
jobQueueParentService.scheduleTypeSafe(deliverRejectJob, deliverRejectJobParam)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
package dev.usbharu.hideout.activitypub.service.activity.undo
|
||||||
|
|
||||||
|
import dev.usbharu.hideout.activitypub.service.common.APRequestService
|
||||||
|
import dev.usbharu.hideout.application.external.Transaction
|
||||||
|
import dev.usbharu.hideout.core.external.job.DeliverUndoJob
|
||||||
|
import dev.usbharu.hideout.core.external.job.DeliverUndoJobParam
|
||||||
|
import dev.usbharu.hideout.core.query.UserQueryService
|
||||||
|
import dev.usbharu.hideout.core.service.job.JobProcessor
|
||||||
|
import org.springframework.stereotype.Service
|
||||||
|
|
||||||
|
@Service
|
||||||
|
class APDeliverUndoJobProcessor(
|
||||||
|
private val deliverUndoJob: DeliverUndoJob,
|
||||||
|
private val apRequestService: APRequestService,
|
||||||
|
private val userQueryService: UserQueryService,
|
||||||
|
private val transaction: Transaction
|
||||||
|
) : JobProcessor<DeliverUndoJobParam, DeliverUndoJob> {
|
||||||
|
override suspend fun process(param: DeliverUndoJobParam): Unit = transaction.transaction {
|
||||||
|
apRequestService.apPost(param.inbox, param.undo, userQueryService.findById(param.signer))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun job(): DeliverUndoJob = deliverUndoJob
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
package dev.usbharu.hideout.activitypub.service.activity.undo
|
||||||
|
|
||||||
|
import dev.usbharu.hideout.core.domain.model.user.User
|
||||||
|
|
||||||
|
interface APSendUndoService {
|
||||||
|
suspend fun sendUndoFollow(user: User, target: User)
|
||||||
|
suspend fun sendUndoBlock(user: User, target: User)
|
||||||
|
}
|
|
@ -0,0 +1,56 @@
|
||||||
|
package dev.usbharu.hideout.activitypub.service.activity.undo
|
||||||
|
|
||||||
|
import dev.usbharu.hideout.activitypub.domain.model.Block
|
||||||
|
import dev.usbharu.hideout.activitypub.domain.model.Follow
|
||||||
|
import dev.usbharu.hideout.activitypub.domain.model.Undo
|
||||||
|
import dev.usbharu.hideout.application.config.ApplicationConfig
|
||||||
|
import dev.usbharu.hideout.core.domain.model.user.User
|
||||||
|
import dev.usbharu.hideout.core.external.job.DeliverUndoJob
|
||||||
|
import dev.usbharu.hideout.core.external.job.DeliverUndoJobParam
|
||||||
|
import dev.usbharu.hideout.core.service.job.JobQueueParentService
|
||||||
|
import org.springframework.stereotype.Service
|
||||||
|
import java.time.Instant
|
||||||
|
|
||||||
|
@Service
|
||||||
|
class APSendUndoServiceImpl(
|
||||||
|
private val jobQueueParentService: JobQueueParentService,
|
||||||
|
private val deliverUndoJob: DeliverUndoJob,
|
||||||
|
private val applicationConfig: ApplicationConfig
|
||||||
|
) : APSendUndoService {
|
||||||
|
override suspend fun sendUndoFollow(user: User, target: User) {
|
||||||
|
val deliverUndoJobParam = DeliverUndoJobParam(
|
||||||
|
Undo(
|
||||||
|
actor = user.url,
|
||||||
|
id = "${applicationConfig.url}/undo/follow/${user.id}/${target.url}",
|
||||||
|
apObject = Follow(
|
||||||
|
apObject = user.url,
|
||||||
|
actor = target.url
|
||||||
|
),
|
||||||
|
published = Instant.now().toString()
|
||||||
|
),
|
||||||
|
target.inbox,
|
||||||
|
user.id
|
||||||
|
)
|
||||||
|
|
||||||
|
jobQueueParentService.scheduleTypeSafe(deliverUndoJob, deliverUndoJobParam)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun sendUndoBlock(user: User, target: User) {
|
||||||
|
val deliverUndoJobParam = DeliverUndoJobParam(
|
||||||
|
Undo(
|
||||||
|
actor = user.url,
|
||||||
|
id = "${applicationConfig.url}/undo/block/${user.id}/${target.url}",
|
||||||
|
apObject = Block(
|
||||||
|
apObject = user.url,
|
||||||
|
actor = target.url,
|
||||||
|
id = "${applicationConfig.url}/block/${user.id}/${target.id}"
|
||||||
|
),
|
||||||
|
published = Instant.now().toString()
|
||||||
|
),
|
||||||
|
target.inbox,
|
||||||
|
user.id
|
||||||
|
)
|
||||||
|
|
||||||
|
jobQueueParentService.scheduleTypeSafe(deliverUndoJob, deliverUndoJobParam)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,14 +1,17 @@
|
||||||
package dev.usbharu.hideout.activitypub.service.activity.undo
|
package dev.usbharu.hideout.activitypub.service.activity.undo
|
||||||
|
|
||||||
|
import dev.usbharu.hideout.activitypub.domain.model.Accept
|
||||||
|
import dev.usbharu.hideout.activitypub.domain.model.Block
|
||||||
import dev.usbharu.hideout.activitypub.domain.model.Follow
|
import dev.usbharu.hideout.activitypub.domain.model.Follow
|
||||||
import dev.usbharu.hideout.activitypub.domain.model.Undo
|
import dev.usbharu.hideout.activitypub.domain.model.Undo
|
||||||
|
import dev.usbharu.hideout.activitypub.domain.model.objects.ObjectValue
|
||||||
import dev.usbharu.hideout.activitypub.service.common.AbstractActivityPubProcessor
|
import dev.usbharu.hideout.activitypub.service.common.AbstractActivityPubProcessor
|
||||||
import dev.usbharu.hideout.activitypub.service.common.ActivityPubProcessContext
|
import dev.usbharu.hideout.activitypub.service.common.ActivityPubProcessContext
|
||||||
import dev.usbharu.hideout.activitypub.service.common.ActivityType
|
import dev.usbharu.hideout.activitypub.service.common.ActivityType
|
||||||
import dev.usbharu.hideout.activitypub.service.objects.user.APUserService
|
import dev.usbharu.hideout.activitypub.service.objects.user.APUserService
|
||||||
import dev.usbharu.hideout.application.external.Transaction
|
import dev.usbharu.hideout.application.external.Transaction
|
||||||
import dev.usbharu.hideout.core.query.UserQueryService
|
import dev.usbharu.hideout.core.query.UserQueryService
|
||||||
import dev.usbharu.hideout.core.service.user.UserService
|
import dev.usbharu.hideout.core.service.relationship.RelationshipService
|
||||||
import org.springframework.stereotype.Service
|
import org.springframework.stereotype.Service
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
|
@ -16,34 +19,57 @@ class APUndoProcessor(
|
||||||
transaction: Transaction,
|
transaction: Transaction,
|
||||||
private val apUserService: APUserService,
|
private val apUserService: APUserService,
|
||||||
private val userQueryService: UserQueryService,
|
private val userQueryService: UserQueryService,
|
||||||
private val userService: UserService
|
private val relationshipService: RelationshipService
|
||||||
) :
|
) :
|
||||||
AbstractActivityPubProcessor<Undo>(transaction) {
|
AbstractActivityPubProcessor<Undo>(transaction) {
|
||||||
override suspend fun internalProcess(activity: ActivityPubProcessContext<Undo>) {
|
override suspend fun internalProcess(activity: ActivityPubProcessContext<Undo>) {
|
||||||
val undo = activity.activity
|
val undo = activity.activity
|
||||||
if (undo.actor == null) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
val type =
|
val type =
|
||||||
undo.`object`.type.orEmpty()
|
undo.apObject.type
|
||||||
.firstOrNull { it == "Block" || it == "Follow" || it == "Like" || it == "Announce" || it == "Accept" }
|
.firstOrNull { it == "Block" || it == "Follow" || it == "Like" || it == "Announce" || it == "Accept" }
|
||||||
?: return
|
?: return
|
||||||
|
|
||||||
when (type) {
|
when (type) {
|
||||||
"Follow" -> {
|
"Follow" -> {
|
||||||
val follow = undo.`object` as Follow
|
val follow = undo.apObject as Follow
|
||||||
|
|
||||||
if (follow.apObject == null) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
apUserService.fetchPerson(undo.actor, follow.apObject)
|
apUserService.fetchPerson(undo.actor, follow.apObject)
|
||||||
val follower = userQueryService.findByUrl(undo.actor)
|
val follower = userQueryService.findByUrl(undo.actor)
|
||||||
val target = userQueryService.findByUrl(follow.apObject)
|
val target = userQueryService.findByUrl(follow.apObject)
|
||||||
userService.unfollow(target.id, follower.id)
|
|
||||||
|
relationshipService.unfollow(follower.id, target.id)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
"Block" -> {
|
||||||
|
val block = undo.apObject as Block
|
||||||
|
|
||||||
|
val blocker = apUserService.fetchPersonWithEntity(undo.actor, block.apObject).second
|
||||||
|
val target = userQueryService.findByUrl(block.apObject)
|
||||||
|
|
||||||
|
relationshipService.unblock(blocker.id, target.id)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
"Accept" -> {
|
||||||
|
val accept = undo.apObject as Accept
|
||||||
|
|
||||||
|
val acceptObject = if (accept.apObject is ObjectValue) {
|
||||||
|
accept.apObject.`object`
|
||||||
|
} else if (accept.apObject is Follow) {
|
||||||
|
accept.apObject.apObject
|
||||||
|
} else {
|
||||||
|
logger.warn("FAILED Unsupported type. Undo Accept {}", accept.apObject.type)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val accepter = apUserService.fetchPersonWithEntity(undo.actor, acceptObject).second
|
||||||
|
val target = userQueryService.findByUrl(acceptObject)
|
||||||
|
|
||||||
|
relationshipService.rejectFollowRequest(accepter.id, target.id)
|
||||||
|
}
|
||||||
|
|
||||||
else -> {}
|
else -> {}
|
||||||
}
|
}
|
||||||
TODO()
|
TODO()
|
||||||
|
|
|
@ -7,9 +7,7 @@ import dev.usbharu.hideout.activitypub.domain.model.objects.Object
|
||||||
import dev.usbharu.hideout.application.external.Transaction
|
import dev.usbharu.hideout.application.external.Transaction
|
||||||
import org.slf4j.Logger
|
import org.slf4j.Logger
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
import org.springframework.stereotype.Service
|
|
||||||
|
|
||||||
@Service
|
|
||||||
abstract class AbstractActivityPubProcessor<T : Object>(
|
abstract class AbstractActivityPubProcessor<T : Object>(
|
||||||
private val transaction: Transaction,
|
private val transaction: Transaction,
|
||||||
private val allowUnauthorized: Boolean = false
|
private val allowUnauthorized: Boolean = false
|
||||||
|
|
|
@ -106,7 +106,7 @@ class InboxJobProcessor(
|
||||||
|
|
||||||
if (activityPubProcessor == null) {
|
if (activityPubProcessor == null) {
|
||||||
logger.warn("ActivityType {} is not support.", param.type)
|
logger.warn("ActivityType {} is not support.", param.type)
|
||||||
throw IllegalStateException("ActivityPubProcessor not found.")
|
throw IllegalStateException("ActivityPubProcessor not found. type: ${param.type}")
|
||||||
}
|
}
|
||||||
|
|
||||||
val value = objectMapper.treeToValue(jsonNode, activityPubProcessor.type())
|
val value = objectMapper.treeToValue(jsonNode, activityPubProcessor.type())
|
||||||
|
|
|
@ -197,6 +197,9 @@ class SecurityConfig {
|
||||||
authorize(GET, "/api/v1/accounts/*", permitAll)
|
authorize(GET, "/api/v1/accounts/*", permitAll)
|
||||||
authorize(GET, "/api/v1/accounts/*/statuses", permitAll)
|
authorize(GET, "/api/v1/accounts/*/statuses", permitAll)
|
||||||
authorize(POST, "/api/v1/accounts/*/follow", hasAnyScope("write", "write:follows"))
|
authorize(POST, "/api/v1/accounts/*/follow", hasAnyScope("write", "write:follows"))
|
||||||
|
authorize(POST, "/api/v1/accounts/*/unfollow", hasAnyScope("write", "write:follows"))
|
||||||
|
authorize(POST, "/api/v1/accounts/*/block", hasAnyScope("write", "write:blocks"))
|
||||||
|
authorize(POST, "/api/v1/accounts/*/unblock", hasAnyScope("write", "write:blocks"))
|
||||||
|
|
||||||
authorize(POST, "/api/v1/media", hasAnyScope("write", "write:media"))
|
authorize(POST, "/api/v1/media", hasAnyScope("write", "write:media"))
|
||||||
authorize(POST, "/api/v1/statuses", hasAnyScope("write", "write:statuses"))
|
authorize(POST, "/api/v1/statuses", hasAnyScope("write", "write:statuses"))
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
package dev.usbharu.hideout.core.domain.model.relationship
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ユーザーとの関係を表します
|
||||||
|
*
|
||||||
|
* @property userId ユーザー
|
||||||
|
* @property targetUserId 相手ユーザー
|
||||||
|
* @property following フォローしているか
|
||||||
|
* @property blocking ブロックしているか
|
||||||
|
* @property muting ミュートしているか
|
||||||
|
* @property followRequest フォローリクエストを送っているか
|
||||||
|
* @property ignoreFollowRequestFromTarget フォローリクエストを無視しているか
|
||||||
|
*/
|
||||||
|
data class Relationship(
|
||||||
|
val userId: Long,
|
||||||
|
val targetUserId: Long,
|
||||||
|
val following: Boolean,
|
||||||
|
val blocking: Boolean,
|
||||||
|
val muting: Boolean,
|
||||||
|
val followRequest: Boolean,
|
||||||
|
val ignoreFollowRequestFromTarget: Boolean
|
||||||
|
)
|
|
@ -0,0 +1,31 @@
|
||||||
|
package dev.usbharu.hideout.core.domain.model.relationship
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [Relationship]の永続化
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
interface RelationshipRepository {
|
||||||
|
/**
|
||||||
|
* 永続化します
|
||||||
|
*
|
||||||
|
* @param relationship 永続化する[Relationship]
|
||||||
|
* @return 永続化された[Relationship]
|
||||||
|
*/
|
||||||
|
suspend fun save(relationship: Relationship): Relationship
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 永続化されたものを削除します
|
||||||
|
*
|
||||||
|
* @param relationship 削除する[Relationship]
|
||||||
|
*/
|
||||||
|
suspend fun delete(relationship: Relationship)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* userIdとtargetUserIdで[Relationship]を取得します
|
||||||
|
*
|
||||||
|
* @param userId 取得するユーザーID
|
||||||
|
* @param targetUserId 対象ユーザーID
|
||||||
|
* @return 取得された[Relationship] 存在しない場合nullが返ります
|
||||||
|
*/
|
||||||
|
suspend fun findByUserIdAndTargetUserId(userId: Long, targetUserId: Long): Relationship?
|
||||||
|
}
|
|
@ -0,0 +1,84 @@
|
||||||
|
package dev.usbharu.hideout.core.domain.model.relationship
|
||||||
|
|
||||||
|
import dev.usbharu.hideout.core.infrastructure.exposedrepository.Users
|
||||||
|
import org.jetbrains.exposed.dao.id.LongIdTable
|
||||||
|
import org.jetbrains.exposed.sql.*
|
||||||
|
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
|
||||||
|
import org.springframework.stereotype.Service
|
||||||
|
|
||||||
|
@Service
|
||||||
|
class RelationshipRepositoryImpl : RelationshipRepository {
|
||||||
|
override suspend fun save(relationship: Relationship): Relationship {
|
||||||
|
val singleOrNull =
|
||||||
|
Relationships
|
||||||
|
.select {
|
||||||
|
(Relationships.userId eq relationship.userId)
|
||||||
|
.and(Relationships.targetUserId eq relationship.targetUserId)
|
||||||
|
}
|
||||||
|
.singleOrNull()
|
||||||
|
|
||||||
|
if (singleOrNull == null) {
|
||||||
|
Relationships.insert {
|
||||||
|
it[userId] = relationship.userId
|
||||||
|
it[targetUserId] = relationship.targetUserId
|
||||||
|
it[following] = relationship.following
|
||||||
|
it[blocking] = relationship.blocking
|
||||||
|
it[muting] = relationship.muting
|
||||||
|
it[followRequest] = relationship.followRequest
|
||||||
|
it[ignoreFollowRequestFromTarget] = relationship.ignoreFollowRequestFromTarget
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Relationships
|
||||||
|
.update({
|
||||||
|
(Relationships.userId eq relationship.userId)
|
||||||
|
.and(Relationships.targetUserId eq relationship.targetUserId)
|
||||||
|
}) {
|
||||||
|
it[following] = relationship.following
|
||||||
|
it[blocking] = relationship.blocking
|
||||||
|
it[muting] = relationship.muting
|
||||||
|
it[followRequest] = relationship.followRequest
|
||||||
|
it[ignoreFollowRequestFromTarget] = relationship.ignoreFollowRequestFromTarget
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return relationship
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun delete(relationship: Relationship) {
|
||||||
|
Relationships.deleteWhere {
|
||||||
|
(Relationships.userId eq relationship.userId)
|
||||||
|
.and(Relationships.targetUserId eq relationship.targetUserId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun findByUserIdAndTargetUserId(userId: Long, targetUserId: Long): Relationship? {
|
||||||
|
return Relationships.select {
|
||||||
|
(Relationships.userId eq userId)
|
||||||
|
.and(Relationships.targetUserId eq targetUserId)
|
||||||
|
}.singleOrNull()
|
||||||
|
?.toRelationships()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun ResultRow.toRelationships(): Relationship = Relationship(
|
||||||
|
userId = this[Relationships.userId],
|
||||||
|
targetUserId = this[Relationships.targetUserId],
|
||||||
|
following = this[Relationships.following],
|
||||||
|
blocking = this[Relationships.blocking],
|
||||||
|
muting = this[Relationships.muting],
|
||||||
|
followRequest = this[Relationships.followRequest],
|
||||||
|
ignoreFollowRequestFromTarget = this[Relationships.ignoreFollowRequestFromTarget]
|
||||||
|
)
|
||||||
|
|
||||||
|
object Relationships : LongIdTable("relationships") {
|
||||||
|
val userId = long("user_id").references(Users.id)
|
||||||
|
val targetUserId = long("target_user_id").references(Users.id)
|
||||||
|
val following = bool("following")
|
||||||
|
val blocking = bool("blocking")
|
||||||
|
val muting = bool("muting")
|
||||||
|
val followRequest = bool("follow_request")
|
||||||
|
val ignoreFollowRequestFromTarget = bool("ignore_follow_request")
|
||||||
|
|
||||||
|
init {
|
||||||
|
uniqueIndex(userId, targetUserId)
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,9 +10,5 @@ interface UserRepository {
|
||||||
|
|
||||||
suspend fun delete(id: Long)
|
suspend fun delete(id: Long)
|
||||||
|
|
||||||
suspend fun deleteFollowRequest(id: Long, follower: Long)
|
|
||||||
|
|
||||||
suspend fun findFollowRequestsById(id: Long, follower: Long): Boolean
|
|
||||||
|
|
||||||
suspend fun nextId(): Long
|
suspend fun nextId(): Long
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
package dev.usbharu.hideout.core.external.job
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper
|
||||||
|
import com.fasterxml.jackson.module.kotlin.readValue
|
||||||
|
import dev.usbharu.hideout.activitypub.domain.model.Accept
|
||||||
|
import kjob.core.dsl.ScheduleContext
|
||||||
|
import kjob.core.job.JobProps
|
||||||
|
import org.springframework.stereotype.Component
|
||||||
|
|
||||||
|
data class DeliverAcceptJobParam(
|
||||||
|
val accept: Accept,
|
||||||
|
val inbox: String,
|
||||||
|
val signer: Long
|
||||||
|
)
|
||||||
|
|
||||||
|
@Component
|
||||||
|
class DeliverAcceptJob(private val objectMapper: ObjectMapper) :
|
||||||
|
HideoutJob<DeliverAcceptJobParam, DeliverAcceptJob>("DeliverAcceptJob") {
|
||||||
|
|
||||||
|
val accept = string("accept")
|
||||||
|
val inbox = string("inbox")
|
||||||
|
val signer = long("signer")
|
||||||
|
|
||||||
|
override fun convert(value: DeliverAcceptJobParam): ScheduleContext<DeliverAcceptJob>.(DeliverAcceptJob) -> Unit = {
|
||||||
|
props[accept] = objectMapper.writeValueAsString(value.accept)
|
||||||
|
props[inbox] = value.inbox
|
||||||
|
props[signer] = value.signer
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun convert(props: JobProps<DeliverAcceptJob>): DeliverAcceptJobParam {
|
||||||
|
return DeliverAcceptJobParam(
|
||||||
|
objectMapper.readValue(props[accept]),
|
||||||
|
props[inbox],
|
||||||
|
props[signer]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,52 @@
|
||||||
|
package dev.usbharu.hideout.core.external.job
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper
|
||||||
|
import com.fasterxml.jackson.module.kotlin.readValue
|
||||||
|
import dev.usbharu.hideout.activitypub.domain.model.Block
|
||||||
|
import dev.usbharu.hideout.activitypub.domain.model.Reject
|
||||||
|
import kjob.core.dsl.ScheduleContext
|
||||||
|
import kjob.core.job.JobProps
|
||||||
|
import org.springframework.beans.factory.annotation.Qualifier
|
||||||
|
import org.springframework.stereotype.Component
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ブロックアクティビティ配送のジョブパラメーター
|
||||||
|
*
|
||||||
|
* @property signer ブロック操作を行ったユーザーid
|
||||||
|
* @property block 配送するブロックアクティビティ
|
||||||
|
* @property reject 配送するフォロー解除アクティビティ
|
||||||
|
* @property inbox 配送先url
|
||||||
|
*/
|
||||||
|
data class DeliverBlockJobParam(
|
||||||
|
val signer: Long,
|
||||||
|
val block: Block,
|
||||||
|
val reject: Reject,
|
||||||
|
val inbox: String
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ブロックアクティビティ配送のジョブ
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
class DeliverBlockJob(@Qualifier("activitypub") private val objectMapper: ObjectMapper) :
|
||||||
|
HideoutJob<DeliverBlockJobParam, DeliverBlockJob>("DeliverBlockJob") {
|
||||||
|
|
||||||
|
val block = string("block")
|
||||||
|
val reject = string("reject")
|
||||||
|
val inbox = string("inbox")
|
||||||
|
val signer = long("signer")
|
||||||
|
|
||||||
|
override fun convert(value: DeliverBlockJobParam): ScheduleContext<DeliverBlockJob>.(DeliverBlockJob) -> Unit = {
|
||||||
|
props[block] = objectMapper.writeValueAsString(value.block)
|
||||||
|
props[reject] = objectMapper.writeValueAsString(value.reject)
|
||||||
|
props[inbox] = value.inbox
|
||||||
|
props[signer] = value.signer
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun convert(props: JobProps<DeliverBlockJob>): DeliverBlockJobParam = DeliverBlockJobParam(
|
||||||
|
signer = props[signer],
|
||||||
|
block = objectMapper.readValue(props[block]),
|
||||||
|
reject = objectMapper.readValue(props[reject]),
|
||||||
|
inbox = props[inbox]
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
package dev.usbharu.hideout.core.external.job
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper
|
||||||
|
import com.fasterxml.jackson.module.kotlin.readValue
|
||||||
|
import dev.usbharu.hideout.activitypub.domain.model.Reject
|
||||||
|
import kjob.core.dsl.ScheduleContext
|
||||||
|
import kjob.core.job.JobProps
|
||||||
|
import org.springframework.beans.factory.annotation.Qualifier
|
||||||
|
import org.springframework.stereotype.Component
|
||||||
|
|
||||||
|
data class DeliverRejectJobParam(
|
||||||
|
val reject: Reject,
|
||||||
|
val inbox: String,
|
||||||
|
val signer: Long
|
||||||
|
)
|
||||||
|
|
||||||
|
@Component
|
||||||
|
class DeliverRejectJob(@Qualifier("activitypub") private val objectMapper: ObjectMapper) :
|
||||||
|
HideoutJob<DeliverRejectJobParam, DeliverRejectJob>("DeliverRejectJob") {
|
||||||
|
val reject = string("reject")
|
||||||
|
val inbox = string("inbox")
|
||||||
|
val signer = long("signer")
|
||||||
|
|
||||||
|
override fun convert(value: DeliverRejectJobParam): ScheduleContext<DeliverRejectJob>.(DeliverRejectJob) -> Unit =
|
||||||
|
{
|
||||||
|
props[reject] = objectMapper.writeValueAsString(value.reject)
|
||||||
|
props[inbox] = value.inbox
|
||||||
|
props[signer] = value.signer
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun convert(props: JobProps<DeliverRejectJob>): DeliverRejectJobParam = DeliverRejectJobParam(
|
||||||
|
objectMapper.readValue<Reject>(props[reject]),
|
||||||
|
props[inbox],
|
||||||
|
props[signer]
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
package dev.usbharu.hideout.core.external.job
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper
|
||||||
|
import com.fasterxml.jackson.module.kotlin.readValue
|
||||||
|
import dev.usbharu.hideout.activitypub.domain.model.Undo
|
||||||
|
import kjob.core.dsl.ScheduleContext
|
||||||
|
import kjob.core.job.JobProps
|
||||||
|
import org.springframework.beans.factory.annotation.Qualifier
|
||||||
|
import org.springframework.stereotype.Component
|
||||||
|
|
||||||
|
data class DeliverUndoJobParam(
|
||||||
|
val undo: Undo,
|
||||||
|
val inbox: String,
|
||||||
|
val signer: Long
|
||||||
|
)
|
||||||
|
|
||||||
|
@Component
|
||||||
|
class DeliverUndoJob(@Qualifier("activitypub") private val objectMapper: ObjectMapper) :
|
||||||
|
HideoutJob<DeliverUndoJobParam, DeliverUndoJob>("DeliverUndoJob") {
|
||||||
|
|
||||||
|
val undo = string("undo")
|
||||||
|
val inbox = string("inbox")
|
||||||
|
val signer = long("signer")
|
||||||
|
|
||||||
|
override fun convert(value: DeliverUndoJobParam): ScheduleContext<DeliverUndoJob>.(DeliverUndoJob) -> Unit = {
|
||||||
|
props[undo] = objectMapper.writeValueAsString(value.undo)
|
||||||
|
props[inbox] = value.inbox
|
||||||
|
props[signer] = value.signer
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun convert(props: JobProps<DeliverUndoJob>): DeliverUndoJobParam {
|
||||||
|
return DeliverUndoJobParam(
|
||||||
|
objectMapper.readValue(props[undo]),
|
||||||
|
props[inbox],
|
||||||
|
props[signer]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,7 +7,7 @@ import kjob.core.dsl.ScheduleContext
|
||||||
import kjob.core.job.JobProps
|
import kjob.core.job.JobProps
|
||||||
import org.springframework.stereotype.Component
|
import org.springframework.stereotype.Component
|
||||||
|
|
||||||
abstract class HideoutJob<out T, out R : HideoutJob<T, R>>(name: String = "") : Job(name) {
|
abstract class HideoutJob<out T, out R : HideoutJob<T, R>>(name: String) : Job(name) {
|
||||||
abstract fun convert(value: @UnsafeVariance T): ScheduleContext<@UnsafeVariance R>.(@UnsafeVariance R) -> Unit
|
abstract fun convert(value: @UnsafeVariance T): ScheduleContext<@UnsafeVariance R>.(@UnsafeVariance R) -> Unit
|
||||||
fun convertUnsafe(props: JobProps<*>): T = convert(props as JobProps<R>)
|
fun convertUnsafe(props: JobProps<*>): T = convert(props as JobProps<R>)
|
||||||
abstract fun convert(props: JobProps<@UnsafeVariance R>): T
|
abstract fun convert(props: JobProps<@UnsafeVariance R>): T
|
||||||
|
|
|
@ -1,242 +1,24 @@
|
||||||
package dev.usbharu.hideout.core.infrastructure.exposedquery
|
package dev.usbharu.hideout.core.infrastructure.exposedquery
|
||||||
|
|
||||||
|
import dev.usbharu.hideout.core.domain.model.relationship.RelationshipRepository
|
||||||
import dev.usbharu.hideout.core.domain.model.user.User
|
import dev.usbharu.hideout.core.domain.model.user.User
|
||||||
import dev.usbharu.hideout.core.infrastructure.exposedrepository.Users
|
|
||||||
import dev.usbharu.hideout.core.infrastructure.exposedrepository.UsersFollowers
|
|
||||||
import dev.usbharu.hideout.core.query.FollowerQueryService
|
import dev.usbharu.hideout.core.query.FollowerQueryService
|
||||||
import org.jetbrains.exposed.sql.*
|
import dev.usbharu.hideout.core.query.RelationshipQueryService
|
||||||
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
|
import dev.usbharu.hideout.core.query.UserQueryService
|
||||||
import org.springframework.stereotype.Repository
|
import org.springframework.stereotype.Repository
|
||||||
import java.time.Instant
|
|
||||||
|
|
||||||
@Repository
|
@Repository
|
||||||
class FollowerQueryServiceImpl(private val userBuilder: User.UserBuilder) : FollowerQueryService {
|
class FollowerQueryServiceImpl(
|
||||||
|
private val relationshipQueryService: RelationshipQueryService,
|
||||||
|
private val userQueryService: UserQueryService,
|
||||||
|
private val relationshipRepository: RelationshipRepository
|
||||||
|
) : FollowerQueryService {
|
||||||
override suspend fun findFollowersById(id: Long): List<User> {
|
override suspend fun findFollowersById(id: Long): List<User> {
|
||||||
val followers = Users.alias("FOLLOWERS")
|
return userQueryService.findByIds(
|
||||||
return Users.innerJoin(
|
relationshipQueryService.findByTargetIdAndFollowing(id, true).map { it.userId }
|
||||||
otherTable = UsersFollowers,
|
|
||||||
onColumn = { Users.id },
|
|
||||||
otherColumn = { userId }
|
|
||||||
)
|
)
|
||||||
.innerJoin(
|
|
||||||
otherTable = followers,
|
|
||||||
onColumn = { UsersFollowers.followerId },
|
|
||||||
otherColumn = { followers[Users.id] }
|
|
||||||
)
|
|
||||||
.slice(
|
|
||||||
followers[Users.id],
|
|
||||||
followers[Users.name],
|
|
||||||
followers[Users.domain],
|
|
||||||
followers[Users.screenName],
|
|
||||||
followers[Users.description],
|
|
||||||
followers[Users.password],
|
|
||||||
followers[Users.inbox],
|
|
||||||
followers[Users.outbox],
|
|
||||||
followers[Users.url],
|
|
||||||
followers[Users.publicKey],
|
|
||||||
followers[Users.privateKey],
|
|
||||||
followers[Users.createdAt],
|
|
||||||
followers[Users.keyId],
|
|
||||||
followers[Users.following],
|
|
||||||
followers[Users.followers],
|
|
||||||
followers[Users.instance]
|
|
||||||
)
|
|
||||||
.select { Users.id eq id }
|
|
||||||
.map {
|
|
||||||
userBuilder.of(
|
|
||||||
id = it[followers[Users.id]],
|
|
||||||
name = it[followers[Users.name]],
|
|
||||||
domain = it[followers[Users.domain]],
|
|
||||||
screenName = it[followers[Users.screenName]],
|
|
||||||
description = it[followers[Users.description]],
|
|
||||||
password = it[followers[Users.password]],
|
|
||||||
inbox = it[followers[Users.inbox]],
|
|
||||||
outbox = it[followers[Users.outbox]],
|
|
||||||
url = it[followers[Users.url]],
|
|
||||||
publicKey = it[followers[Users.publicKey]],
|
|
||||||
privateKey = it[followers[Users.privateKey]],
|
|
||||||
createdAt = Instant.ofEpochMilli(it[followers[Users.createdAt]]),
|
|
||||||
keyId = it[followers[Users.keyId]],
|
|
||||||
followers = it[followers[Users.followers]],
|
|
||||||
following = it[followers[Users.following]],
|
|
||||||
instance = it[followers[Users.instance]]
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun findFollowersByNameAndDomain(name: String, domain: String): List<User> {
|
override suspend fun alreadyFollow(userId: Long, followerId: Long): Boolean =
|
||||||
val followers = Users.alias("FOLLOWERS")
|
relationshipRepository.findByUserIdAndTargetUserId(followerId, userId)?.following ?: false
|
||||||
return Users.innerJoin(
|
|
||||||
otherTable = UsersFollowers,
|
|
||||||
onColumn = { id },
|
|
||||||
otherColumn = { userId }
|
|
||||||
)
|
|
||||||
.innerJoin(
|
|
||||||
otherTable = followers,
|
|
||||||
onColumn = { UsersFollowers.followerId },
|
|
||||||
otherColumn = { followers[Users.id] }
|
|
||||||
)
|
|
||||||
.slice(
|
|
||||||
followers[Users.id],
|
|
||||||
followers[Users.name],
|
|
||||||
followers[Users.domain],
|
|
||||||
followers[Users.screenName],
|
|
||||||
followers[Users.description],
|
|
||||||
followers[Users.password],
|
|
||||||
followers[Users.inbox],
|
|
||||||
followers[Users.outbox],
|
|
||||||
followers[Users.url],
|
|
||||||
followers[Users.publicKey],
|
|
||||||
followers[Users.privateKey],
|
|
||||||
followers[Users.createdAt],
|
|
||||||
followers[Users.keyId],
|
|
||||||
followers[Users.following],
|
|
||||||
followers[Users.followers],
|
|
||||||
followers[Users.instance]
|
|
||||||
)
|
|
||||||
.select { Users.name eq name and (Users.domain eq domain) }
|
|
||||||
.map {
|
|
||||||
userBuilder.of(
|
|
||||||
id = it[followers[Users.id]],
|
|
||||||
name = it[followers[Users.name]],
|
|
||||||
domain = it[followers[Users.domain]],
|
|
||||||
screenName = it[followers[Users.screenName]],
|
|
||||||
description = it[followers[Users.description]],
|
|
||||||
password = it[followers[Users.password]],
|
|
||||||
inbox = it[followers[Users.inbox]],
|
|
||||||
outbox = it[followers[Users.outbox]],
|
|
||||||
url = it[followers[Users.url]],
|
|
||||||
publicKey = it[followers[Users.publicKey]],
|
|
||||||
privateKey = it[followers[Users.privateKey]],
|
|
||||||
createdAt = Instant.ofEpochMilli(it[followers[Users.createdAt]]),
|
|
||||||
keyId = it[followers[Users.keyId]],
|
|
||||||
followers = it[followers[Users.followers]],
|
|
||||||
following = it[followers[Users.following]],
|
|
||||||
instance = it[followers[Users.instance]]
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun findFollowingById(id: Long): List<User> {
|
|
||||||
val followers = Users.alias("FOLLOWERS")
|
|
||||||
return Users.innerJoin(
|
|
||||||
otherTable = UsersFollowers,
|
|
||||||
onColumn = { Users.id },
|
|
||||||
otherColumn = { userId }
|
|
||||||
)
|
|
||||||
.innerJoin(
|
|
||||||
otherTable = followers,
|
|
||||||
onColumn = { UsersFollowers.followerId },
|
|
||||||
otherColumn = { followers[Users.id] }
|
|
||||||
)
|
|
||||||
.slice(
|
|
||||||
followers[Users.id],
|
|
||||||
followers[Users.name],
|
|
||||||
followers[Users.domain],
|
|
||||||
followers[Users.screenName],
|
|
||||||
followers[Users.description],
|
|
||||||
followers[Users.password],
|
|
||||||
followers[Users.inbox],
|
|
||||||
followers[Users.outbox],
|
|
||||||
followers[Users.url],
|
|
||||||
followers[Users.publicKey],
|
|
||||||
followers[Users.privateKey],
|
|
||||||
followers[Users.createdAt],
|
|
||||||
followers[Users.keyId],
|
|
||||||
followers[Users.following],
|
|
||||||
followers[Users.followers],
|
|
||||||
followers[Users.instance]
|
|
||||||
)
|
|
||||||
.select { followers[Users.id] eq id }
|
|
||||||
.map {
|
|
||||||
userBuilder.of(
|
|
||||||
id = it[followers[Users.id]],
|
|
||||||
name = it[followers[Users.name]],
|
|
||||||
domain = it[followers[Users.domain]],
|
|
||||||
screenName = it[followers[Users.screenName]],
|
|
||||||
description = it[followers[Users.description]],
|
|
||||||
password = it[followers[Users.password]],
|
|
||||||
inbox = it[followers[Users.inbox]],
|
|
||||||
outbox = it[followers[Users.outbox]],
|
|
||||||
url = it[followers[Users.url]],
|
|
||||||
publicKey = it[followers[Users.publicKey]],
|
|
||||||
privateKey = it[followers[Users.privateKey]],
|
|
||||||
createdAt = Instant.ofEpochMilli(it[followers[Users.createdAt]]),
|
|
||||||
keyId = it[followers[Users.keyId]],
|
|
||||||
followers = it[followers[Users.followers]],
|
|
||||||
following = it[followers[Users.following]],
|
|
||||||
instance = it[followers[Users.instance]]
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun findFollowingByNameAndDomain(name: String, domain: String): List<User> {
|
|
||||||
val followers = Users.alias("FOLLOWERS")
|
|
||||||
return Users.innerJoin(
|
|
||||||
otherTable = UsersFollowers,
|
|
||||||
onColumn = { id },
|
|
||||||
otherColumn = { userId }
|
|
||||||
)
|
|
||||||
.innerJoin(
|
|
||||||
otherTable = followers,
|
|
||||||
onColumn = { UsersFollowers.followerId },
|
|
||||||
otherColumn = { followers[Users.id] }
|
|
||||||
)
|
|
||||||
.slice(
|
|
||||||
followers[Users.id],
|
|
||||||
followers[Users.name],
|
|
||||||
followers[Users.domain],
|
|
||||||
followers[Users.screenName],
|
|
||||||
followers[Users.description],
|
|
||||||
followers[Users.password],
|
|
||||||
followers[Users.inbox],
|
|
||||||
followers[Users.outbox],
|
|
||||||
followers[Users.url],
|
|
||||||
followers[Users.publicKey],
|
|
||||||
followers[Users.privateKey],
|
|
||||||
followers[Users.createdAt],
|
|
||||||
followers[Users.keyId],
|
|
||||||
followers[Users.following],
|
|
||||||
followers[Users.followers],
|
|
||||||
followers[Users.instance]
|
|
||||||
)
|
|
||||||
.select { followers[Users.name] eq name and (followers[Users.domain] eq domain) }
|
|
||||||
.map {
|
|
||||||
userBuilder.of(
|
|
||||||
id = it[followers[Users.id]],
|
|
||||||
name = it[followers[Users.name]],
|
|
||||||
domain = it[followers[Users.domain]],
|
|
||||||
screenName = it[followers[Users.screenName]],
|
|
||||||
description = it[followers[Users.description]],
|
|
||||||
password = it[followers[Users.password]],
|
|
||||||
inbox = it[followers[Users.inbox]],
|
|
||||||
outbox = it[followers[Users.outbox]],
|
|
||||||
url = it[followers[Users.url]],
|
|
||||||
publicKey = it[followers[Users.publicKey]],
|
|
||||||
privateKey = it[followers[Users.privateKey]],
|
|
||||||
createdAt = Instant.ofEpochMilli(it[followers[Users.createdAt]]),
|
|
||||||
keyId = it[followers[Users.keyId]],
|
|
||||||
followers = it[followers[Users.followers]],
|
|
||||||
following = it[followers[Users.following]],
|
|
||||||
instance = it[followers[Users.instance]]
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun appendFollower(user: Long, follower: Long) {
|
|
||||||
UsersFollowers.insert {
|
|
||||||
it[userId] = user
|
|
||||||
it[followerId] = follower
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun removeFollower(user: Long, follower: Long) {
|
|
||||||
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()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
package dev.usbharu.hideout.core.infrastructure.exposedquery
|
||||||
|
|
||||||
|
import dev.usbharu.hideout.core.domain.model.relationship.Relationship
|
||||||
|
import dev.usbharu.hideout.core.domain.model.relationship.Relationships
|
||||||
|
import dev.usbharu.hideout.core.domain.model.relationship.toRelationships
|
||||||
|
import dev.usbharu.hideout.core.query.RelationshipQueryService
|
||||||
|
import org.jetbrains.exposed.sql.and
|
||||||
|
import org.jetbrains.exposed.sql.select
|
||||||
|
import org.springframework.stereotype.Service
|
||||||
|
|
||||||
|
@Service
|
||||||
|
class RelationshipQueryServiceImpl : RelationshipQueryService {
|
||||||
|
override suspend fun findByTargetIdAndFollowing(targetId: Long, following: Boolean): List<Relationship> =
|
||||||
|
Relationships.select { Relationships.targetUserId eq targetId and (Relationships.following eq following) }
|
||||||
|
.map { it.toRelationships() }
|
||||||
|
}
|
|
@ -4,7 +4,6 @@ import dev.usbharu.hideout.application.infrastructure.exposed.ResultRowMapper
|
||||||
import dev.usbharu.hideout.application.service.id.IdGenerateService
|
import dev.usbharu.hideout.application.service.id.IdGenerateService
|
||||||
import dev.usbharu.hideout.core.domain.model.user.User
|
import dev.usbharu.hideout.core.domain.model.user.User
|
||||||
import dev.usbharu.hideout.core.domain.model.user.UserRepository
|
import dev.usbharu.hideout.core.domain.model.user.UserRepository
|
||||||
import org.jetbrains.exposed.dao.id.LongIdTable
|
|
||||||
import org.jetbrains.exposed.sql.*
|
import org.jetbrains.exposed.sql.*
|
||||||
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
|
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
|
||||||
import org.springframework.stereotype.Repository
|
import org.springframework.stereotype.Repository
|
||||||
|
@ -62,15 +61,6 @@ class UserRepositoryImpl(
|
||||||
override suspend fun findById(id: Long): User? =
|
override suspend fun findById(id: Long): User? =
|
||||||
Users.select { Users.id eq id }.singleOrNull()?.let(userResultRowMapper::map)
|
Users.select { Users.id eq id }.singleOrNull()?.let(userResultRowMapper::map)
|
||||||
|
|
||||||
override suspend fun deleteFollowRequest(id: Long, follower: Long) {
|
|
||||||
FollowRequests.deleteWhere { userId.eq(id) and followerId.eq(follower) }
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun findFollowRequestsById(id: Long, follower: Long): Boolean {
|
|
||||||
return 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) {
|
||||||
Users.deleteWhere { Users.id.eq(id) }
|
Users.deleteWhere { Users.id.eq(id) }
|
||||||
}
|
}
|
||||||
|
@ -108,21 +98,3 @@ object Users : Table("users") {
|
||||||
uniqueIndex(name, domain)
|
uniqueIndex(name, domain)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
object UsersFollowers : LongIdTable("users_followers") {
|
|
||||||
val userId: Column<Long> = long("user_id").references(Users.id).index()
|
|
||||||
val followerId: Column<Long> = long("follower_id").references(Users.id)
|
|
||||||
|
|
||||||
init {
|
|
||||||
uniqueIndex(userId, followerId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
object FollowRequests : LongIdTable("follow_requests") {
|
|
||||||
val userId: Column<Long> = long("user_id").references(Users.id)
|
|
||||||
val followerId: Column<Long> = long("follower_id").references(Users.id)
|
|
||||||
|
|
||||||
init {
|
|
||||||
uniqueIndex(userId, followerId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -36,10 +36,14 @@ class KJobJobQueueWorkerService(private val jobQueueProcessorList: List<JobProce
|
||||||
for (jobProcessor in jobQueueProcessorList) {
|
for (jobProcessor in jobQueueProcessorList) {
|
||||||
kjob.register(jobProcessor.job()) {
|
kjob.register(jobProcessor.job()) {
|
||||||
execute {
|
execute {
|
||||||
|
@Suppress("TooGenericExceptionCaught")
|
||||||
try {
|
try {
|
||||||
MDC.put("x-job-id", this.jobId)
|
MDC.put("x-job-id", this.jobId)
|
||||||
val param = it.convertUnsafe(props)
|
val param = it.convertUnsafe(props)
|
||||||
jobProcessor.process(param)
|
jobProcessor.process(param)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
logger.warn("FAILED Execute Job. job name: {} job id: {}", it.name, this.jobId, e)
|
||||||
|
throw e
|
||||||
} finally {
|
} finally {
|
||||||
MDC.remove("x-job-id")
|
MDC.remove("x-job-id")
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,12 +2,8 @@ package dev.usbharu.hideout.core.query
|
||||||
|
|
||||||
import dev.usbharu.hideout.core.domain.model.user.User
|
import dev.usbharu.hideout.core.domain.model.user.User
|
||||||
|
|
||||||
|
@Deprecated("Use RelationshipQueryService")
|
||||||
interface FollowerQueryService {
|
interface FollowerQueryService {
|
||||||
suspend fun findFollowersById(id: Long): List<User>
|
suspend fun findFollowersById(id: Long): List<User>
|
||||||
suspend fun findFollowersByNameAndDomain(name: String, domain: String): List<User>
|
|
||||||
suspend fun findFollowingById(id: Long): List<User>
|
|
||||||
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
|
suspend fun alreadyFollow(userId: Long, followerId: Long): Boolean
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
package dev.usbharu.hideout.core.query
|
||||||
|
|
||||||
|
import dev.usbharu.hideout.core.domain.model.relationship.Relationship
|
||||||
|
|
||||||
|
interface RelationshipQueryService {
|
||||||
|
|
||||||
|
suspend fun findByTargetIdAndFollowing(targetId: Long, following: Boolean): List<Relationship>
|
||||||
|
}
|
|
@ -36,6 +36,7 @@ class LocalFileSystemMediaDataStore(
|
||||||
savePath.createDirectories()
|
savePath.createDirectories()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("NestedBlockDepth")
|
||||||
override suspend fun save(dataMediaSave: MediaSave): SavedMedia {
|
override suspend fun save(dataMediaSave: MediaSave): SavedMedia {
|
||||||
val fileSavePath = buildSavePath(savePath, dataMediaSave.name)
|
val fileSavePath = buildSavePath(savePath, dataMediaSave.name)
|
||||||
val thumbnailSavePath = buildSavePath(savePath, "thumbnail-" + dataMediaSave.name)
|
val thumbnailSavePath = buildSavePath(savePath, "thumbnail-" + dataMediaSave.name)
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
package dev.usbharu.hideout.core.service.relationship
|
||||||
|
|
||||||
|
interface RelationshipService {
|
||||||
|
suspend fun followRequest(userId: Long, targetId: Long)
|
||||||
|
suspend fun block(userId: Long, targetId: Long)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* フォローリクエストを承認します
|
||||||
|
* [userId]が[targetId]からのフォローリクエストを承認します
|
||||||
|
*
|
||||||
|
* @param userId 承認操作をするユーザー
|
||||||
|
* @param targetId 承認するフォローリクエストを送ってきたユーザー
|
||||||
|
* @param force 強制的にAcceptアクティビティを発行する
|
||||||
|
*/
|
||||||
|
suspend fun acceptFollowRequest(userId: Long, targetId: Long, force: Boolean = false)
|
||||||
|
suspend fun rejectFollowRequest(userId: Long, targetId: Long)
|
||||||
|
suspend fun ignoreFollowRequest(userId: Long, targetId: Long)
|
||||||
|
suspend fun unfollow(userId: Long, targetId: Long)
|
||||||
|
suspend fun unblock(userId: Long, targetId: Long)
|
||||||
|
suspend fun mute(userId: Long, targetId: Long)
|
||||||
|
suspend fun unmute(userId: Long, targetId: Long)
|
||||||
|
}
|
|
@ -0,0 +1,305 @@
|
||||||
|
package dev.usbharu.hideout.core.service.relationship
|
||||||
|
|
||||||
|
import dev.usbharu.hideout.activitypub.service.activity.accept.ApSendAcceptService
|
||||||
|
import dev.usbharu.hideout.activitypub.service.activity.block.APSendBlockService
|
||||||
|
import dev.usbharu.hideout.activitypub.service.activity.follow.APSendFollowService
|
||||||
|
import dev.usbharu.hideout.activitypub.service.activity.reject.ApSendRejectService
|
||||||
|
import dev.usbharu.hideout.activitypub.service.activity.undo.APSendUndoService
|
||||||
|
import dev.usbharu.hideout.application.config.ApplicationConfig
|
||||||
|
import dev.usbharu.hideout.core.domain.exception.FailedToGetResourcesException
|
||||||
|
import dev.usbharu.hideout.core.domain.model.relationship.Relationship
|
||||||
|
import dev.usbharu.hideout.core.domain.model.relationship.RelationshipRepository
|
||||||
|
import dev.usbharu.hideout.core.domain.model.user.User
|
||||||
|
import dev.usbharu.hideout.core.query.UserQueryService
|
||||||
|
import dev.usbharu.hideout.core.service.follow.SendFollowDto
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
|
import org.springframework.stereotype.Service
|
||||||
|
|
||||||
|
@Service
|
||||||
|
class RelationshipServiceImpl(
|
||||||
|
private val applicationConfig: ApplicationConfig,
|
||||||
|
private val userQueryService: UserQueryService,
|
||||||
|
private val relationshipRepository: RelationshipRepository,
|
||||||
|
private val apSendFollowService: APSendFollowService,
|
||||||
|
private val apSendBlockService: APSendBlockService,
|
||||||
|
private val apSendAcceptService: ApSendAcceptService,
|
||||||
|
private val apSendRejectService: ApSendRejectService,
|
||||||
|
private val apSendUndoService: APSendUndoService
|
||||||
|
) : RelationshipService {
|
||||||
|
override suspend fun followRequest(userId: Long, targetId: Long) {
|
||||||
|
logger.info("START Follow Request userId: {} targetId: {}", userId, targetId)
|
||||||
|
|
||||||
|
val relationship =
|
||||||
|
relationshipRepository.findByUserIdAndTargetUserId(userId, targetId)?.copy(followRequest = true)
|
||||||
|
?: Relationship(
|
||||||
|
userId = userId,
|
||||||
|
targetUserId = targetId,
|
||||||
|
following = false,
|
||||||
|
blocking = false,
|
||||||
|
muting = false,
|
||||||
|
followRequest = true,
|
||||||
|
ignoreFollowRequestFromTarget = false
|
||||||
|
)
|
||||||
|
|
||||||
|
val inverseRelationship = relationshipRepository.findByUserIdAndTargetUserId(targetId, userId) ?: Relationship(
|
||||||
|
userId = targetId,
|
||||||
|
targetUserId = userId,
|
||||||
|
following = false,
|
||||||
|
blocking = false,
|
||||||
|
muting = false,
|
||||||
|
followRequest = false,
|
||||||
|
ignoreFollowRequestFromTarget = false
|
||||||
|
)
|
||||||
|
|
||||||
|
if (inverseRelationship.blocking) {
|
||||||
|
logger.debug("FAILED Blocked by target. userId: {} targetId: {}", userId, targetId)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (relationship.blocking) {
|
||||||
|
logger.debug("FAILED Blocking user. userId: {} targetId: {}", userId, targetId)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (inverseRelationship.ignoreFollowRequestFromTarget) {
|
||||||
|
logger.debug("SUCCESS Ignore Follow Request. userId: {} targetId: {}", userId, targetId)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (relationship.following) {
|
||||||
|
logger.debug("SUCCESS User already follow. userId: {} targetId: {}", userId, targetId)
|
||||||
|
acceptFollowRequest(targetId, userId, true)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
relationshipRepository.save(relationship)
|
||||||
|
|
||||||
|
val remoteUser = isRemoteUser(targetId)
|
||||||
|
|
||||||
|
if (remoteUser != null) {
|
||||||
|
val user = userQueryService.findById(userId)
|
||||||
|
apSendFollowService.sendFollow(SendFollowDto(user, remoteUser))
|
||||||
|
} else {
|
||||||
|
// TODO: フォロー許可制ユーザーを実装したら消す
|
||||||
|
acceptFollowRequest(targetId, userId)
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info("SUCCESS Follow Request userId: {} targetId: {}", userId, targetId)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun block(userId: Long, targetId: Long) {
|
||||||
|
val relationship = relationshipRepository.findByUserIdAndTargetUserId(userId, targetId)
|
||||||
|
?.copy(blocking = true, followRequest = false, following = false) ?: Relationship(
|
||||||
|
userId = userId,
|
||||||
|
targetUserId = targetId,
|
||||||
|
following = false,
|
||||||
|
blocking = true,
|
||||||
|
muting = false,
|
||||||
|
followRequest = false,
|
||||||
|
ignoreFollowRequestFromTarget = false
|
||||||
|
)
|
||||||
|
|
||||||
|
val inverseRelationship = relationshipRepository.findByUserIdAndTargetUserId(targetId, userId)
|
||||||
|
?.copy(followRequest = false, following = false)
|
||||||
|
|
||||||
|
relationshipRepository.save(relationship)
|
||||||
|
if (inverseRelationship != null) {
|
||||||
|
relationshipRepository.save(inverseRelationship)
|
||||||
|
}
|
||||||
|
|
||||||
|
val remoteUser = isRemoteUser(targetId)
|
||||||
|
|
||||||
|
if (remoteUser != null) {
|
||||||
|
val user = userQueryService.findById(userId)
|
||||||
|
apSendBlockService.sendBlock(user, remoteUser)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun acceptFollowRequest(userId: Long, targetId: Long, force: Boolean) {
|
||||||
|
logger.info("START Accept follow request userId: {} targetId: {}", userId, targetId)
|
||||||
|
|
||||||
|
val relationship = relationshipRepository.findByUserIdAndTargetUserId(targetId, userId)
|
||||||
|
|
||||||
|
val inverseRelationship = relationshipRepository.findByUserIdAndTargetUserId(userId, targetId) ?: Relationship(
|
||||||
|
userId = targetId,
|
||||||
|
targetUserId = userId,
|
||||||
|
following = false,
|
||||||
|
blocking = false,
|
||||||
|
muting = false,
|
||||||
|
followRequest = false,
|
||||||
|
ignoreFollowRequestFromTarget = false
|
||||||
|
)
|
||||||
|
|
||||||
|
if (relationship == null) {
|
||||||
|
logger.warn("FAILED Follow Request Not Found. (Relationship) userId: {} targetId: {}", userId, targetId)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (relationship.followRequest.not() && force.not()) {
|
||||||
|
logger.warn("FAILED Follow Request Not Found. (Follow Request) userId: {} targetId: {}", userId, targetId)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (relationship.blocking) {
|
||||||
|
logger.warn("FAILED Blocking user userId: {} targetId: {}", userId, targetId)
|
||||||
|
throw IllegalStateException(
|
||||||
|
"Cannot accept a follow request from a blocked user. userId: $userId targetId: $targetId"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (inverseRelationship.blocking) {
|
||||||
|
logger.warn("FAILED BLocked by user userId: {} targetId: {}", userId, targetId)
|
||||||
|
throw IllegalStateException(
|
||||||
|
"Cannot accept a follow request from a blocking user. userId: $userId targetId: $targetId"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val copy = relationship.copy(followRequest = false, following = true, blocking = false)
|
||||||
|
|
||||||
|
relationshipRepository.save(copy)
|
||||||
|
|
||||||
|
val remoteUser = isRemoteUser(targetId)
|
||||||
|
|
||||||
|
if (remoteUser != null) {
|
||||||
|
val user = userQueryService.findById(userId)
|
||||||
|
apSendAcceptService.sendAcceptFollow(user, remoteUser)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun rejectFollowRequest(userId: Long, targetId: Long) {
|
||||||
|
val relationship = relationshipRepository.findByUserIdAndTargetUserId(targetId, userId)
|
||||||
|
|
||||||
|
if (relationship == null) {
|
||||||
|
logger.warn("FAILED Follow Request Not Found. (Relationship) userId: {} targetId: {}", userId, targetId)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (relationship.followRequest.not() && relationship.following.not()) {
|
||||||
|
logger.warn("FAILED Follow Request Not Found. (Follow Request) userId: {} targetId: {}", userId, targetId)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val copy = relationship.copy(followRequest = false, following = false)
|
||||||
|
|
||||||
|
relationshipRepository.save(copy)
|
||||||
|
|
||||||
|
val remoteUser = isRemoteUser(targetId)
|
||||||
|
|
||||||
|
if (remoteUser != null) {
|
||||||
|
val user = userQueryService.findById(userId)
|
||||||
|
apSendRejectService.sendRejectFollow(user, remoteUser)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun ignoreFollowRequest(userId: Long, targetId: Long) {
|
||||||
|
val relationship = relationshipRepository.findByUserIdAndTargetUserId(userId, targetId)
|
||||||
|
?.copy(ignoreFollowRequestFromTarget = true)
|
||||||
|
?: Relationship(
|
||||||
|
userId = userId,
|
||||||
|
targetUserId = targetId,
|
||||||
|
following = false,
|
||||||
|
blocking = false,
|
||||||
|
muting = false,
|
||||||
|
followRequest = false,
|
||||||
|
ignoreFollowRequestFromTarget = true
|
||||||
|
)
|
||||||
|
|
||||||
|
relationshipRepository.save(relationship)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun unfollow(userId: Long, targetId: Long) {
|
||||||
|
val relationship = relationshipRepository.findByUserIdAndTargetUserId(userId, targetId)
|
||||||
|
|
||||||
|
if (relationship == null) {
|
||||||
|
logger.warn("FAILED Unfollow. (Relationship) userId: {} targetId: {}", userId, targetId)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (relationship.following.not()) {
|
||||||
|
logger.warn("SUCCESS User already unfollow. userId: {} targetId: {}", userId, targetId)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val copy = relationship.copy(following = false)
|
||||||
|
|
||||||
|
relationshipRepository.save(copy)
|
||||||
|
|
||||||
|
val remoteUser = isRemoteUser(targetId)
|
||||||
|
|
||||||
|
if (remoteUser != null) {
|
||||||
|
val user = userQueryService.findById(userId)
|
||||||
|
apSendUndoService.sendUndoFollow(user, remoteUser)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun unblock(userId: Long, targetId: Long) {
|
||||||
|
val relationship = relationshipRepository.findByUserIdAndTargetUserId(userId, targetId)
|
||||||
|
|
||||||
|
if (relationship == null) {
|
||||||
|
logger.warn("FAILED Unblock. (Relationship) userId: {} targetId: {}", userId, targetId)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (relationship.blocking.not()) {
|
||||||
|
logger.warn("SUCCESS User is not blocking. userId: {] targetId: {}", userId, targetId)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val copy = relationship.copy(blocking = false)
|
||||||
|
relationshipRepository.save(copy)
|
||||||
|
|
||||||
|
val remoteUser = isRemoteUser(targetId)
|
||||||
|
if (remoteUser != null) {
|
||||||
|
val user = userQueryService.findById(userId)
|
||||||
|
apSendUndoService.sendUndoBlock(user, remoteUser)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun mute(userId: Long, targetId: Long) {
|
||||||
|
val relationship = relationshipRepository.findByUserIdAndTargetUserId(userId, targetId)?.copy(muting = true)
|
||||||
|
?: Relationship(
|
||||||
|
userId = userId,
|
||||||
|
targetUserId = targetId,
|
||||||
|
following = false,
|
||||||
|
blocking = false,
|
||||||
|
muting = true,
|
||||||
|
followRequest = false,
|
||||||
|
ignoreFollowRequestFromTarget = false
|
||||||
|
)
|
||||||
|
|
||||||
|
relationshipRepository.save(relationship)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun unmute(userId: Long, targetId: Long) {
|
||||||
|
val relationship = relationshipRepository.findByUserIdAndTargetUserId(userId, targetId)?.copy(muting = false)
|
||||||
|
|
||||||
|
if (relationship == null) {
|
||||||
|
logger.warn("FAILED Mute. (Relationship) userId: {} targetId: {}", userId, targetId)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
relationshipRepository.save(relationship)
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun isRemoteUser(userId: Long): User? {
|
||||||
|
logger.trace("isRemoteUser({})", userId)
|
||||||
|
val user = try {
|
||||||
|
userQueryService.findById(userId)
|
||||||
|
} catch (e: FailedToGetResourcesException) {
|
||||||
|
logger.warn("User not found.", e)
|
||||||
|
throw IllegalStateException("User not found.", e)
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.trace("user info {}", user)
|
||||||
|
|
||||||
|
if (user.domain == applicationConfig.url.host) {
|
||||||
|
logger.trace("user: {} is local user", userId)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
logger.trace("user: {} is remote user", userId)
|
||||||
|
return user
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val logger = LoggerFactory.getLogger(RelationshipServiceImpl::class.java)
|
||||||
|
}
|
||||||
|
}
|
|
@ -11,23 +11,4 @@ interface UserService {
|
||||||
suspend fun createLocalUser(user: UserCreateDto): User
|
suspend fun createLocalUser(user: UserCreateDto): User
|
||||||
|
|
||||||
suspend fun createRemoteUser(user: RemoteUserCreateDto): User
|
suspend fun createRemoteUser(user: RemoteUserCreateDto): User
|
||||||
|
|
||||||
/**
|
|
||||||
* フォローリクエストを送信する
|
|
||||||
*
|
|
||||||
* @param id
|
|
||||||
* @param followerId
|
|
||||||
* @return リクエストが成功したか
|
|
||||||
*/
|
|
||||||
suspend fun followRequest(id: Long, followerId: Long): Boolean
|
|
||||||
|
|
||||||
/**
|
|
||||||
* フォローする
|
|
||||||
*
|
|
||||||
* @param id
|
|
||||||
* @param followerId
|
|
||||||
*/
|
|
||||||
suspend fun follow(id: Long, followerId: Long)
|
|
||||||
|
|
||||||
suspend fun unfollow(id: Long, followerId: Long): Boolean
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,9 @@
|
||||||
package dev.usbharu.hideout.core.service.user
|
package dev.usbharu.hideout.core.service.user
|
||||||
|
|
||||||
import dev.usbharu.hideout.activitypub.service.activity.follow.APSendFollowService
|
|
||||||
import dev.usbharu.hideout.application.config.ApplicationConfig
|
import dev.usbharu.hideout.application.config.ApplicationConfig
|
||||||
import dev.usbharu.hideout.core.domain.exception.UserNotFoundException
|
|
||||||
import dev.usbharu.hideout.core.domain.model.user.User
|
import dev.usbharu.hideout.core.domain.model.user.User
|
||||||
import dev.usbharu.hideout.core.domain.model.user.UserRepository
|
import dev.usbharu.hideout.core.domain.model.user.UserRepository
|
||||||
import dev.usbharu.hideout.core.query.FollowerQueryService
|
|
||||||
import dev.usbharu.hideout.core.query.UserQueryService
|
import dev.usbharu.hideout.core.query.UserQueryService
|
||||||
import dev.usbharu.hideout.core.service.follow.SendFollowDto
|
|
||||||
import dev.usbharu.hideout.core.service.instance.InstanceService
|
import dev.usbharu.hideout.core.service.instance.InstanceService
|
||||||
import org.jetbrains.exposed.exceptions.ExposedSQLException
|
import org.jetbrains.exposed.exceptions.ExposedSQLException
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
|
@ -19,9 +15,7 @@ import java.time.Instant
|
||||||
class UserServiceImpl(
|
class UserServiceImpl(
|
||||||
private val userRepository: UserRepository,
|
private val userRepository: UserRepository,
|
||||||
private val userAuthService: UserAuthService,
|
private val userAuthService: UserAuthService,
|
||||||
private val apSendFollowService: APSendFollowService,
|
|
||||||
private val userQueryService: UserQueryService,
|
private val userQueryService: UserQueryService,
|
||||||
private val followerQueryService: FollowerQueryService,
|
|
||||||
private val userBuilder: User.UserBuilder,
|
private val userBuilder: User.UserBuilder,
|
||||||
private val applicationConfig: ApplicationConfig,
|
private val applicationConfig: ApplicationConfig,
|
||||||
private val instanceService: InstanceService
|
private val instanceService: InstanceService
|
||||||
|
@ -96,38 +90,6 @@ class UserServiceImpl(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO APのフォロー処理を作る
|
|
||||||
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.")
|
|
||||||
return if (user.domain == applicationConfig.url.host) {
|
|
||||||
follow(id, followerId)
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
if (userRepository.findFollowRequestsById(id, followerId)) {
|
|
||||||
// do-nothing
|
|
||||||
} else {
|
|
||||||
apSendFollowService.sendFollow(SendFollowDto(follower, user))
|
|
||||||
}
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun follow(id: Long, followerId: Long) {
|
|
||||||
logger.debug("START Follow id: {} → target: {}", followerId, id)
|
|
||||||
followerQueryService.appendFollower(id, followerId)
|
|
||||||
if (userRepository.findFollowRequestsById(id, followerId)) {
|
|
||||||
logger.debug("Follow request is accepted! ")
|
|
||||||
userRepository.deleteFollowRequest(id, followerId)
|
|
||||||
}
|
|
||||||
logger.debug("SUCCESS Follow id: {} → target: {}", followerId, id)
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun unfollow(id: Long, followerId: Long): Boolean {
|
|
||||||
followerQueryService.removeFollower(id, followerId)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val logger = LoggerFactory.getLogger(UserServiceImpl::class.java)
|
private val logger = LoggerFactory.getLogger(UserServiceImpl::class.java)
|
||||||
}
|
}
|
||||||
|
|
|
@ -75,17 +75,17 @@ class MastodonAccountApiController(
|
||||||
|
|
||||||
val userid = principal.getClaim<String>("uid").toLong()
|
val userid = principal.getClaim<String>("uid").toLong()
|
||||||
val statusFlow = accountApiService.accountsStatuses(
|
val statusFlow = accountApiService.accountsStatuses(
|
||||||
id.toLong(),
|
userid = id.toLong(),
|
||||||
maxId?.toLongOrNull(),
|
maxId = maxId?.toLongOrNull(),
|
||||||
sinceId?.toLongOrNull(),
|
sinceId = sinceId?.toLongOrNull(),
|
||||||
minId?.toLongOrNull(),
|
minId = minId?.toLongOrNull(),
|
||||||
limit,
|
limit = limit,
|
||||||
onlyMedia,
|
onlyMedia = onlyMedia,
|
||||||
excludeReplies,
|
excludeReplies = excludeReplies,
|
||||||
excludeReblogs,
|
excludeReblogs = excludeReblogs,
|
||||||
pinned,
|
pinned = pinned,
|
||||||
tagged,
|
tagged = tagged,
|
||||||
userid
|
loginUser = userid
|
||||||
).asFlow()
|
).asFlow()
|
||||||
ResponseEntity.ok(statusFlow)
|
ResponseEntity.ok(statusFlow)
|
||||||
}
|
}
|
||||||
|
@ -103,4 +103,44 @@ class MastodonAccountApiController(
|
||||||
.asFlow()
|
.asFlow()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun apiV1AccountsIdBlockPost(id: String): ResponseEntity<Relationship> {
|
||||||
|
val principal = SecurityContextHolder.getContext().getAuthentication().principal as Jwt
|
||||||
|
|
||||||
|
val userid = principal.getClaim<String>("uid").toLong()
|
||||||
|
|
||||||
|
val block = accountApiService.block(userid, id.toLong())
|
||||||
|
|
||||||
|
return ResponseEntity.ok(block)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun apiV1AccountsIdUnblockPost(id: String): ResponseEntity<Relationship> {
|
||||||
|
val principal = SecurityContextHolder.getContext().getAuthentication().principal as Jwt
|
||||||
|
|
||||||
|
val userid = principal.getClaim<String>("uid").toLong()
|
||||||
|
|
||||||
|
val unblock = accountApiService.unblock(userid, id.toLong())
|
||||||
|
|
||||||
|
return ResponseEntity.ok(unblock)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun apiV1AccountsIdUnfollowPost(id: String): ResponseEntity<Relationship> {
|
||||||
|
val principal = SecurityContextHolder.getContext().getAuthentication().principal as Jwt
|
||||||
|
|
||||||
|
val userid = principal.getClaim<String>("uid").toLong()
|
||||||
|
|
||||||
|
val unfollow = accountApiService.unfollow(userid, id.toLong())
|
||||||
|
|
||||||
|
return ResponseEntity.ok(unfollow)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun apiV1AccountsIdRemoveFromFollowersPost(id: String): ResponseEntity<Relationship> {
|
||||||
|
val principal = SecurityContextHolder.getContext().getAuthentication().principal as Jwt
|
||||||
|
|
||||||
|
val userid = principal.getClaim<String>("uid").toLong()
|
||||||
|
|
||||||
|
val removeFromFollowers = accountApiService.removeFromFollowers(userid, id.toLong())
|
||||||
|
|
||||||
|
return ResponseEntity.ok(removeFromFollowers)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
package dev.usbharu.hideout.mastodon.service.account
|
package dev.usbharu.hideout.mastodon.service.account
|
||||||
|
|
||||||
import dev.usbharu.hideout.application.external.Transaction
|
import dev.usbharu.hideout.application.external.Transaction
|
||||||
import dev.usbharu.hideout.core.domain.model.user.UserRepository
|
import dev.usbharu.hideout.core.domain.model.relationship.RelationshipRepository
|
||||||
import dev.usbharu.hideout.core.query.FollowerQueryService
|
import dev.usbharu.hideout.core.service.relationship.RelationshipService
|
||||||
import dev.usbharu.hideout.core.service.user.UserCreateDto
|
import dev.usbharu.hideout.core.service.user.UserCreateDto
|
||||||
import dev.usbharu.hideout.core.service.user.UserService
|
import dev.usbharu.hideout.core.service.user.UserService
|
||||||
import dev.usbharu.hideout.domain.mastodon.model.generated.*
|
import dev.usbharu.hideout.domain.mastodon.model.generated.*
|
||||||
|
@ -13,6 +13,7 @@ import kotlin.math.min
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
interface AccountApiService {
|
interface AccountApiService {
|
||||||
|
@Suppress("LongParameterList")
|
||||||
suspend fun accountsStatuses(
|
suspend fun accountsStatuses(
|
||||||
userid: Long,
|
userid: Long,
|
||||||
maxId: Long?,
|
maxId: Long?,
|
||||||
|
@ -29,9 +30,21 @@ interface AccountApiService {
|
||||||
|
|
||||||
suspend fun verifyCredentials(userid: Long): CredentialAccount
|
suspend fun verifyCredentials(userid: Long): CredentialAccount
|
||||||
suspend fun registerAccount(userCreateDto: UserCreateDto): Unit
|
suspend fun registerAccount(userCreateDto: UserCreateDto): Unit
|
||||||
suspend fun follow(userid: Long, followeeId: Long): Relationship
|
suspend fun follow(loginUser: Long, followTargetUserId: Long): Relationship
|
||||||
suspend fun account(id: Long): Account
|
suspend fun account(id: Long): Account
|
||||||
suspend fun relationships(userid: Long, id: List<Long>, withSuspended: Boolean): List<Relationship>
|
suspend fun relationships(userid: Long, id: List<Long>, withSuspended: Boolean): List<Relationship>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ブロック操作を行う
|
||||||
|
*
|
||||||
|
* @param userid ブロック操作を行ったユーザーid
|
||||||
|
* @param target ブロック対象のユーザーid
|
||||||
|
* @return ブロック後のブロック対象ユーザーとの[Relationship]
|
||||||
|
*/
|
||||||
|
suspend fun block(userid: Long, target: Long): Relationship
|
||||||
|
suspend fun unblock(userid: Long, target: Long): Relationship
|
||||||
|
suspend fun unfollow(userid: Long, target: Long): Relationship
|
||||||
|
suspend fun removeFromFollowers(userid: Long, target: Long): Relationship
|
||||||
}
|
}
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
|
@ -39,9 +52,9 @@ class AccountApiServiceImpl(
|
||||||
private val accountService: AccountService,
|
private val accountService: AccountService,
|
||||||
private val transaction: Transaction,
|
private val transaction: Transaction,
|
||||||
private val userService: UserService,
|
private val userService: UserService,
|
||||||
private val followerQueryService: FollowerQueryService,
|
private val statusQueryService: StatusQueryService,
|
||||||
private val userRepository: UserRepository,
|
private val relationshipService: RelationshipService,
|
||||||
private val statusQueryService: StatusQueryService
|
private val relationshipRepository: RelationshipRepository
|
||||||
) :
|
) :
|
||||||
AccountApiService {
|
AccountApiService {
|
||||||
override suspend fun accountsStatuses(
|
override suspend fun accountsStatuses(
|
||||||
|
@ -61,23 +74,23 @@ class AccountApiServiceImpl(
|
||||||
false
|
false
|
||||||
} else {
|
} else {
|
||||||
transaction.transaction {
|
transaction.transaction {
|
||||||
followerQueryService.alreadyFollow(userid, loginUser)
|
isFollowing(loginUser, userid)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return transaction.transaction {
|
return transaction.transaction {
|
||||||
statusQueryService.accountsStatus(
|
statusQueryService.accountsStatus(
|
||||||
userid,
|
accountId = userid,
|
||||||
maxId,
|
maxId = maxId,
|
||||||
sinceId,
|
sinceId = sinceId,
|
||||||
minId,
|
minId = minId,
|
||||||
limit,
|
limit = limit,
|
||||||
onlyMedia,
|
onlyMedia = onlyMedia,
|
||||||
excludeReplies,
|
excludeReplies = excludeReplies,
|
||||||
excludeReblogs,
|
excludeReblogs = excludeReblogs,
|
||||||
pinned,
|
pinned = pinned,
|
||||||
tagged,
|
tagged = tagged,
|
||||||
canViewFollowers
|
includeFollowers = canViewFollowers
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -91,34 +104,10 @@ class AccountApiServiceImpl(
|
||||||
userService.createLocalUser(UserCreateDto(userCreateDto.name, userCreateDto.name, "", userCreateDto.password))
|
userService.createLocalUser(UserCreateDto(userCreateDto.name, userCreateDto.name, "", userCreateDto.password))
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun follow(userid: Long, followeeId: Long): Relationship = transaction.transaction {
|
override suspend fun follow(loginUser: Long, followTargetUserId: Long): Relationship = transaction.transaction {
|
||||||
val alreadyFollow = followerQueryService.alreadyFollow(followeeId, userid)
|
relationshipService.followRequest(loginUser, followTargetUserId)
|
||||||
|
|
||||||
val followRequest = if (alreadyFollow) {
|
return@transaction fetchRelationship(loginUser, followTargetUserId)
|
||||||
true
|
|
||||||
} else {
|
|
||||||
userService.followRequest(followeeId, userid)
|
|
||||||
}
|
|
||||||
|
|
||||||
val alreadyFollow1 = followerQueryService.alreadyFollow(userid, followeeId)
|
|
||||||
|
|
||||||
val followRequestsById = userRepository.findFollowRequestsById(followeeId, userid)
|
|
||||||
|
|
||||||
return@transaction Relationship(
|
|
||||||
followeeId.toString(),
|
|
||||||
followRequest,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
alreadyFollow1,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
followRequestsById,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
""
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun account(id: Long): Account = transaction.transaction {
|
override suspend fun account(id: Long): Account = transaction.transaction {
|
||||||
|
@ -136,30 +125,34 @@ class AccountApiServiceImpl(
|
||||||
val subList = id.subList(0, min(id.size, 20))
|
val subList = id.subList(0, min(id.size, 20))
|
||||||
|
|
||||||
return@transaction subList.map {
|
return@transaction subList.map {
|
||||||
val alreadyFollow = followerQueryService.alreadyFollow(userid, it)
|
fetchRelationship(userid, it)
|
||||||
|
|
||||||
val followed = followerQueryService.alreadyFollow(it, userid)
|
|
||||||
|
|
||||||
val requested = userRepository.findFollowRequestsById(it, userid)
|
|
||||||
|
|
||||||
Relationship(
|
|
||||||
id = it.toString(),
|
|
||||||
following = alreadyFollow,
|
|
||||||
showingReblogs = true,
|
|
||||||
notifying = false,
|
|
||||||
followedBy = followed,
|
|
||||||
blocking = false,
|
|
||||||
blockedBy = false,
|
|
||||||
muting = false,
|
|
||||||
mutingNotifications = false,
|
|
||||||
requested = requested,
|
|
||||||
domainBlocking = false,
|
|
||||||
endorsed = false,
|
|
||||||
note = ""
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun block(userid: Long, target: Long): Relationship = transaction.transaction {
|
||||||
|
relationshipService.block(userid, target)
|
||||||
|
|
||||||
|
fetchRelationship(userid, target)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun unblock(userid: Long, target: Long): Relationship = transaction.transaction {
|
||||||
|
relationshipService.unblock(userid, target)
|
||||||
|
|
||||||
|
return@transaction fetchRelationship(userid, target)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun unfollow(userid: Long, target: Long): Relationship = transaction.transaction {
|
||||||
|
relationshipService.unfollow(userid, target)
|
||||||
|
|
||||||
|
return@transaction fetchRelationship(userid, target)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun removeFromFollowers(userid: Long, target: Long): Relationship = transaction.transaction {
|
||||||
|
relationshipService.rejectFollowRequest(userid, target)
|
||||||
|
|
||||||
|
return@transaction fetchRelationship(userid, target)
|
||||||
|
}
|
||||||
|
|
||||||
private fun from(account: Account): CredentialAccount {
|
private fun from(account: Account): CredentialAccount {
|
||||||
return CredentialAccount(
|
return CredentialAccount(
|
||||||
id = account.id,
|
id = account.id,
|
||||||
|
@ -198,6 +191,49 @@ class AccountApiServiceImpl(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private suspend fun fetchRelationship(userid: Long, targetId: Long): Relationship {
|
||||||
|
val relationship = relationshipRepository.findByUserIdAndTargetUserId(userid, targetId)
|
||||||
|
?: dev.usbharu.hideout.core.domain.model.relationship.Relationship(
|
||||||
|
userId = userid,
|
||||||
|
targetUserId = targetId,
|
||||||
|
following = false,
|
||||||
|
blocking = false,
|
||||||
|
muting = false,
|
||||||
|
followRequest = false,
|
||||||
|
ignoreFollowRequestFromTarget = false
|
||||||
|
)
|
||||||
|
|
||||||
|
val inverseRelationship = relationshipRepository.findByUserIdAndTargetUserId(targetId, userid)
|
||||||
|
?: dev.usbharu.hideout.core.domain.model.relationship.Relationship(
|
||||||
|
userId = targetId,
|
||||||
|
targetUserId = userid,
|
||||||
|
following = false,
|
||||||
|
blocking = false,
|
||||||
|
muting = false,
|
||||||
|
followRequest = false,
|
||||||
|
ignoreFollowRequestFromTarget = false
|
||||||
|
)
|
||||||
|
|
||||||
|
return Relationship(
|
||||||
|
id = targetId.toString(),
|
||||||
|
following = relationship.following,
|
||||||
|
showingReblogs = true,
|
||||||
|
notifying = false,
|
||||||
|
followedBy = inverseRelationship.following,
|
||||||
|
blocking = relationship.blocking,
|
||||||
|
blockedBy = inverseRelationship.blocking,
|
||||||
|
muting = relationship.muting,
|
||||||
|
mutingNotifications = relationship.muting,
|
||||||
|
requested = relationship.followRequest,
|
||||||
|
domainBlocking = false,
|
||||||
|
endorsed = false,
|
||||||
|
note = ""
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun isFollowing(userid: Long, target: Long): Boolean =
|
||||||
|
relationshipRepository.findByUserIdAndTargetUserId(userid, target)?.following ?: false
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val logger = LoggerFactory.getLogger(AccountApiServiceImpl::class.java)
|
private val logger = LoggerFactory.getLogger(AccountApiServiceImpl::class.java)
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,12 +55,12 @@ class AppApiServiceImpl(
|
||||||
registeredClientRepository.save(registeredClient)
|
registeredClientRepository.save(registeredClient)
|
||||||
|
|
||||||
Application(
|
Application(
|
||||||
appsRequest.clientName,
|
name = appsRequest.clientName,
|
||||||
"invalid-vapid-key",
|
vapidKey = "invalid-vapid-key",
|
||||||
appsRequest.website,
|
website = appsRequest.website,
|
||||||
id,
|
clientId = id,
|
||||||
clientSecret,
|
clientSecret = clientSecret,
|
||||||
appsRequest.redirectUris
|
redirectUri = appsRequest.redirectUris
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,14 +34,7 @@ create table if not exists users
|
||||||
unique ("name", "domain"),
|
unique ("name", "domain"),
|
||||||
constraint fk_users_instance__id foreign key ("instance") references instance (id) on delete restrict on update restrict
|
constraint fk_users_instance__id foreign key ("instance") references instance (id) on delete restrict on update restrict
|
||||||
);
|
);
|
||||||
create table if not exists follow_requests
|
|
||||||
(
|
|
||||||
id bigserial primary key,
|
|
||||||
user_id bigint not null,
|
|
||||||
follower_id bigint not null,
|
|
||||||
constraint fk_follow_requests_user_id__id foreign key (user_id) references users (id) on delete restrict on update restrict,
|
|
||||||
constraint fk_follow_requests_follower_id__id foreign key (follower_id) references users (id) on delete restrict on update restrict
|
|
||||||
);
|
|
||||||
create table if not exists media
|
create table if not exists media
|
||||||
(
|
(
|
||||||
id bigint primary key,
|
id bigint primary key,
|
||||||
|
@ -119,14 +112,7 @@ create table if not exists timelines
|
||||||
is_pure_repost boolean not null,
|
is_pure_repost boolean not null,
|
||||||
media_ids varchar(255) not null
|
media_ids varchar(255) not null
|
||||||
);
|
);
|
||||||
create table if not exists users_followers
|
|
||||||
(
|
|
||||||
id bigserial primary key,
|
|
||||||
user_id bigint not null,
|
|
||||||
follower_id bigint not null,
|
|
||||||
constraint fk_users_followers_user_id__id foreign key (user_id) references users (id) on delete restrict on update restrict,
|
|
||||||
constraint fk_users_followers_follower_id__id foreign key (follower_id) references users (id) on delete restrict on update restrict
|
|
||||||
);
|
|
||||||
create table if not exists application_authorization
|
create table if not exists application_authorization
|
||||||
(
|
(
|
||||||
id varchar(255) primary key,
|
id varchar(255) primary key,
|
||||||
|
@ -186,4 +172,19 @@ create table if not exists registered_client
|
||||||
scopes varchar(1000) not null,
|
scopes varchar(1000) not null,
|
||||||
client_settings varchar(2000) not null,
|
client_settings varchar(2000) not null,
|
||||||
token_settings varchar(2000) not null
|
token_settings varchar(2000) not null
|
||||||
|
);
|
||||||
|
|
||||||
|
create table if not exists relationships
|
||||||
|
(
|
||||||
|
id bigserial primary key,
|
||||||
|
user_id bigint not null,
|
||||||
|
target_user_id bigint not null,
|
||||||
|
following boolean not null,
|
||||||
|
blocking boolean not null,
|
||||||
|
muting boolean not null,
|
||||||
|
follow_request boolean not null,
|
||||||
|
ignore_follow_request boolean not null,
|
||||||
|
constraint fk_relationships_user_id__id foreign key (user_id) references users (id) on delete restrict on update restrict,
|
||||||
|
constraint fk_relationships_target_user_id__id foreign key (target_user_id) references users (id) on delete restrict on update restrict,
|
||||||
|
unique (user_id, target_user_id)
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
<configuration>
|
<configuration>
|
||||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||||
<encoder>
|
<encoder>
|
||||||
<pattern>%d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %-5level [%X{x-request-id}] %logger{36} - %msg%n</pattern>
|
<pattern>%d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %-5level [%X{x-request-id}] [%X{x-job-id}] %logger{36} -
|
||||||
|
%msg%n
|
||||||
|
</pattern>
|
||||||
</encoder>
|
</encoder>
|
||||||
</appender>
|
</appender>
|
||||||
<root level="DEBUG">
|
<root level="DEBUG">
|
||||||
|
|
|
@ -287,6 +287,89 @@ paths:
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/components/schemas/Relationship"
|
$ref: "#/components/schemas/Relationship"
|
||||||
|
|
||||||
|
/api/v1/accounts/{id}/block:
|
||||||
|
post:
|
||||||
|
tags:
|
||||||
|
- account
|
||||||
|
security:
|
||||||
|
- OAuth2:
|
||||||
|
- "write:blocks"
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
name: id
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: 成功
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/Relationship"
|
||||||
|
|
||||||
|
/api/v1/accounts/{id}/unfollow:
|
||||||
|
post:
|
||||||
|
tags:
|
||||||
|
- account
|
||||||
|
security:
|
||||||
|
- OAuth2:
|
||||||
|
- "write:follows"
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
name: id
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: 成功
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/Relationship"
|
||||||
|
|
||||||
|
/api/v1/accounts/{id}/unblock:
|
||||||
|
post:
|
||||||
|
tags:
|
||||||
|
- account
|
||||||
|
security:
|
||||||
|
- OAuth2:
|
||||||
|
- "write:blocks"
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
name: id
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: 成功
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/Relationship"
|
||||||
|
/api/v1/accounts/{id}/remove_from_followers:
|
||||||
|
post:
|
||||||
|
tags:
|
||||||
|
- account
|
||||||
|
security:
|
||||||
|
- OAuth2:
|
||||||
|
- "write:follows"
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
name: id
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: 成功
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/Relationship"
|
||||||
|
|
||||||
/api/v1/accounts/{id}/statuses:
|
/api/v1/accounts/{id}/statuses:
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
|
|
|
@ -0,0 +1,78 @@
|
||||||
|
package dev.usbharu.hideout.activitypub.domain.model
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.module.kotlin.readValue
|
||||||
|
import dev.usbharu.hideout.application.config.ActivityPubConfig
|
||||||
|
import org.assertj.core.api.Assertions.assertThat
|
||||||
|
import org.intellij.lang.annotations.Language
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.springframework.boot.test.json.BasicJsonTester
|
||||||
|
|
||||||
|
class BlockTest {
|
||||||
|
@Test
|
||||||
|
fun blockDeserializeTest() {
|
||||||
|
@Language("JSON") val json = """{
|
||||||
|
"@context" : [ "https://www.w3.org/ns/activitystreams", "https://w3id.org/security/v1", {
|
||||||
|
"manuallyApprovesFollowers" : "as:manuallyApprovesFollowers",
|
||||||
|
"sensitive" : "as:sensitive",
|
||||||
|
"Hashtag" : "as:Hashtag",
|
||||||
|
"quoteUrl" : "as:quoteUrl",
|
||||||
|
"toot" : "http://joinmastodon.org/ns#",
|
||||||
|
"Emoji" : "toot:Emoji",
|
||||||
|
"featured" : "toot:featured",
|
||||||
|
"discoverable" : "toot:discoverable",
|
||||||
|
"schema" : "http://schema.org#",
|
||||||
|
"PropertyValue" : "schema:PropertyValue",
|
||||||
|
"value" : "schema:value",
|
||||||
|
"misskey" : "https://misskey-hub.net/ns#",
|
||||||
|
"_misskey_content" : "misskey:_misskey_content",
|
||||||
|
"_misskey_quote" : "misskey:_misskey_quote",
|
||||||
|
"_misskey_reaction" : "misskey:_misskey_reaction",
|
||||||
|
"_misskey_votes" : "misskey:_misskey_votes",
|
||||||
|
"_misskey_summary" : "misskey:_misskey_summary",
|
||||||
|
"isCat" : "misskey:isCat",
|
||||||
|
"vcard" : "http://www.w3.org/2006/vcard/ns#"
|
||||||
|
} ],
|
||||||
|
"type" : "Block",
|
||||||
|
"id" : "https://misskey.usbharu.dev/blocks/9myf6e40vm",
|
||||||
|
"actor" : "https://misskey.usbharu.dev/users/97ws8y3rj6",
|
||||||
|
"object" : "https://test-hideout.usbharu.dev/users/test-user2"
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
val objectMapper = ActivityPubConfig().objectMapper()
|
||||||
|
|
||||||
|
val block = objectMapper.readValue<Block>(json)
|
||||||
|
|
||||||
|
val expected = Block(
|
||||||
|
"https://misskey.usbharu.dev/users/97ws8y3rj6",
|
||||||
|
"https://misskey.usbharu.dev/blocks/9myf6e40vm",
|
||||||
|
"https://test-hideout.usbharu.dev/users/test-user2"
|
||||||
|
).apply { context = listOf("https://www.w3.org/ns/activitystreams", "https://w3id.org/security/v1") }
|
||||||
|
assertThat(block).isEqualTo(expected)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun blockSerializeTest() {
|
||||||
|
val basicJsonTester = BasicJsonTester(javaClass)
|
||||||
|
|
||||||
|
val block = Block(
|
||||||
|
"https://misskey.usbharu.dev/users/97ws8y3rj6",
|
||||||
|
"https://misskey.usbharu.dev/blocks/9myf6e40vm",
|
||||||
|
"https://test-hideout.usbharu.dev/users/test-user2"
|
||||||
|
).apply { context = listOf("https://www.w3.org/ns/activitystreams", "https://w3id.org/security/v1") }
|
||||||
|
|
||||||
|
val objectMapper = ActivityPubConfig().objectMapper()
|
||||||
|
|
||||||
|
val writeValueAsString = objectMapper.writeValueAsString(block)
|
||||||
|
|
||||||
|
val from = basicJsonTester.from(writeValueAsString)
|
||||||
|
assertThat(from).extractingJsonPathStringValue("$.actor")
|
||||||
|
.isEqualTo("https://misskey.usbharu.dev/users/97ws8y3rj6")
|
||||||
|
assertThat(from).extractingJsonPathStringValue("$.id")
|
||||||
|
.isEqualTo("https://misskey.usbharu.dev/blocks/9myf6e40vm")
|
||||||
|
assertThat(from).extractingJsonPathStringValue("$.object")
|
||||||
|
.isEqualTo("https://test-hideout.usbharu.dev/users/test-user2")
|
||||||
|
assertThat(from).extractingJsonPathStringValue("$.type").isEqualTo("Block")
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,93 @@
|
||||||
|
package dev.usbharu.hideout.activitypub.domain.model
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.module.kotlin.readValue
|
||||||
|
import dev.usbharu.hideout.application.config.ActivityPubConfig
|
||||||
|
import org.assertj.core.api.Assertions.assertThat
|
||||||
|
import org.intellij.lang.annotations.Language
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.springframework.boot.test.json.BasicJsonTester
|
||||||
|
|
||||||
|
class RejectTest {
|
||||||
|
@Test
|
||||||
|
fun rejectDeserializeTest() {
|
||||||
|
@Language("JSON") val json = """{
|
||||||
|
"@context" : [ "https://www.w3.org/ns/activitystreams", "https://w3id.org/security/v1", {
|
||||||
|
"manuallyApprovesFollowers" : "as:manuallyApprovesFollowers",
|
||||||
|
"sensitive" : "as:sensitive",
|
||||||
|
"Hashtag" : "as:Hashtag",
|
||||||
|
"quoteUrl" : "as:quoteUrl",
|
||||||
|
"toot" : "http://joinmastodon.org/ns#",
|
||||||
|
"Emoji" : "toot:Emoji",
|
||||||
|
"featured" : "toot:featured",
|
||||||
|
"discoverable" : "toot:discoverable",
|
||||||
|
"schema" : "http://schema.org#",
|
||||||
|
"PropertyValue" : "schema:PropertyValue",
|
||||||
|
"value" : "schema:value",
|
||||||
|
"misskey" : "https://misskey-hub.net/ns#",
|
||||||
|
"_misskey_content" : "misskey:_misskey_content",
|
||||||
|
"_misskey_quote" : "misskey:_misskey_quote",
|
||||||
|
"_misskey_reaction" : "misskey:_misskey_reaction",
|
||||||
|
"_misskey_votes" : "misskey:_misskey_votes",
|
||||||
|
"_misskey_summary" : "misskey:_misskey_summary",
|
||||||
|
"isCat" : "misskey:isCat",
|
||||||
|
"vcard" : "http://www.w3.org/2006/vcard/ns#"
|
||||||
|
} ],
|
||||||
|
"type" : "Reject",
|
||||||
|
"actor" : "https://misskey.usbharu.dev/users/97ws8y3rj6",
|
||||||
|
"object" : {
|
||||||
|
"id" : "https://misskey.usbharu.dev/follows/9mxh6mawru/97ws8y3rj6",
|
||||||
|
"type" : "Follow",
|
||||||
|
"actor" : "https://test-hideout.usbharu.dev/users/test-user2",
|
||||||
|
"object" : "https://misskey.usbharu.dev/users/97ws8y3rj6"
|
||||||
|
},
|
||||||
|
"id" : "https://misskey.usbharu.dev/06407419-5aeb-4e2d-8885-aa54b03decf0"
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
val objectMapper = ActivityPubConfig().objectMapper()
|
||||||
|
|
||||||
|
val reject = objectMapper.readValue<Reject>(json)
|
||||||
|
|
||||||
|
val expected = Reject(
|
||||||
|
"https://misskey.usbharu.dev/users/97ws8y3rj6",
|
||||||
|
"https://misskey.usbharu.dev/06407419-5aeb-4e2d-8885-aa54b03decf0",
|
||||||
|
Follow(
|
||||||
|
apObject = "https://misskey.usbharu.dev/users/97ws8y3rj6",
|
||||||
|
actor = "https://test-hideout.usbharu.dev/users/test-user2"
|
||||||
|
)
|
||||||
|
).apply { context = listOf("https://www.w3.org/ns/activitystreams", "https://w3id.org/security/v1") }
|
||||||
|
|
||||||
|
assertThat(reject).isEqualTo(expected)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun rejectSerializeTest() {
|
||||||
|
val basicJsonTester = BasicJsonTester(javaClass)
|
||||||
|
|
||||||
|
val reject = Reject(
|
||||||
|
"https://misskey.usbharu.dev/users/97ws8y3rj6",
|
||||||
|
"https://misskey.usbharu.dev/06407419-5aeb-4e2d-8885-aa54b03decf0",
|
||||||
|
Follow(
|
||||||
|
apObject = "https://misskey.usbharu.dev/users/97ws8y3rj6",
|
||||||
|
actor = "https://test-hideout.usbharu.dev/users/test-user2"
|
||||||
|
)
|
||||||
|
).apply { context = listOf("https://www.w3.org/ns/activitystreams", "https://w3id.org/security/v1") }
|
||||||
|
|
||||||
|
val objectMapper = ActivityPubConfig().objectMapper()
|
||||||
|
|
||||||
|
val writeValueAsString = objectMapper.writeValueAsString(reject)
|
||||||
|
|
||||||
|
val from = basicJsonTester.from(writeValueAsString)
|
||||||
|
|
||||||
|
assertThat(from).extractingJsonPathStringValue("$.actor")
|
||||||
|
.isEqualTo("https://misskey.usbharu.dev/users/97ws8y3rj6")
|
||||||
|
assertThat(from).extractingJsonPathStringValue("$.id")
|
||||||
|
.isEqualTo("https://misskey.usbharu.dev/06407419-5aeb-4e2d-8885-aa54b03decf0")
|
||||||
|
assertThat(from).extractingJsonPathStringValue("$.type").isEqualTo("Reject")
|
||||||
|
assertThat(from).extractingJsonPathStringValue("$.object.actor")
|
||||||
|
.isEqualTo("https://test-hideout.usbharu.dev/users/test-user2")
|
||||||
|
assertThat(from).extractingJsonPathStringValue("$.object.object")
|
||||||
|
.isEqualTo("https://misskey.usbharu.dev/users/97ws8y3rj6")
|
||||||
|
assertThat(from).extractingJsonPathStringValue("$.object.type").isEqualTo("Follow")
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,70 @@
|
||||||
|
package dev.usbharu.hideout.activitypub.service.activity.accept
|
||||||
|
|
||||||
|
import dev.usbharu.hideout.activitypub.domain.model.Accept
|
||||||
|
import dev.usbharu.hideout.activitypub.domain.model.Follow
|
||||||
|
import dev.usbharu.hideout.activitypub.service.common.APRequestService
|
||||||
|
import dev.usbharu.hideout.core.external.job.DeliverAcceptJob
|
||||||
|
import dev.usbharu.hideout.core.external.job.DeliverAcceptJobParam
|
||||||
|
import dev.usbharu.hideout.core.query.UserQueryService
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
import org.assertj.core.api.Assertions.assertThat
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith
|
||||||
|
import org.mockito.InjectMocks
|
||||||
|
import org.mockito.Mock
|
||||||
|
import org.mockito.Spy
|
||||||
|
import org.mockito.junit.jupiter.MockitoExtension
|
||||||
|
import org.mockito.kotlin.*
|
||||||
|
import utils.TestTransaction
|
||||||
|
import utils.UserBuilder
|
||||||
|
|
||||||
|
@ExtendWith(MockitoExtension::class)
|
||||||
|
class APDeliverAcceptJobProcessorTest {
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private lateinit var apRequestService: APRequestService
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private lateinit var userQueryService: UserQueryService
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private lateinit var deliverAcceptJob: DeliverAcceptJob
|
||||||
|
|
||||||
|
@Spy
|
||||||
|
private val transaction = TestTransaction
|
||||||
|
|
||||||
|
@InjectMocks
|
||||||
|
private lateinit var apDeliverAcceptJobProcessor: APDeliverAcceptJobProcessor
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `process apPostが発行される`() = runTest {
|
||||||
|
val user = UserBuilder.localUserOf()
|
||||||
|
|
||||||
|
whenever(userQueryService.findById(eq(1))).doReturn(user)
|
||||||
|
|
||||||
|
val accept = Accept(
|
||||||
|
apObject = Follow(
|
||||||
|
apObject = "https://example.com",
|
||||||
|
actor = "https://remote.example.com"
|
||||||
|
),
|
||||||
|
actor = "https://example.com"
|
||||||
|
)
|
||||||
|
val param = DeliverAcceptJobParam(
|
||||||
|
accept = accept,
|
||||||
|
"https://remote.example.com",
|
||||||
|
1
|
||||||
|
)
|
||||||
|
|
||||||
|
apDeliverAcceptJobProcessor.process(param)
|
||||||
|
|
||||||
|
verify(apRequestService, times(1)).apPost(eq("https://remote.example.com"), eq(accept), eq(user))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `job DeliverAcceptJobが返ってくる`() {
|
||||||
|
val actual = apDeliverAcceptJobProcessor.job()
|
||||||
|
|
||||||
|
assertThat(actual).isEqualTo(deliverAcceptJob)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,130 @@
|
||||||
|
package dev.usbharu.hideout.activitypub.service.activity.accept
|
||||||
|
|
||||||
|
import dev.usbharu.hideout.activitypub.domain.exception.IllegalActivityPubObjectException
|
||||||
|
import dev.usbharu.hideout.activitypub.domain.model.Accept
|
||||||
|
import dev.usbharu.hideout.activitypub.domain.model.Follow
|
||||||
|
import dev.usbharu.hideout.activitypub.domain.model.Like
|
||||||
|
import dev.usbharu.hideout.activitypub.service.common.ActivityPubProcessContext
|
||||||
|
import dev.usbharu.hideout.activitypub.service.common.ActivityType
|
||||||
|
import dev.usbharu.hideout.application.config.ActivityPubConfig
|
||||||
|
import dev.usbharu.hideout.core.query.UserQueryService
|
||||||
|
import dev.usbharu.hideout.core.service.relationship.RelationshipService
|
||||||
|
import dev.usbharu.httpsignature.common.HttpHeaders
|
||||||
|
import dev.usbharu.httpsignature.common.HttpMethod
|
||||||
|
import dev.usbharu.httpsignature.common.HttpRequest
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
import org.assertj.core.api.Assertions.assertThat
|
||||||
|
import org.junit.jupiter.api.DynamicTest
|
||||||
|
import org.junit.jupiter.api.DynamicTest.dynamicTest
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.junit.jupiter.api.TestFactory
|
||||||
|
import org.junit.jupiter.api.assertThrows
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith
|
||||||
|
import org.mockito.InjectMocks
|
||||||
|
import org.mockito.Mock
|
||||||
|
import org.mockito.Spy
|
||||||
|
import org.mockito.junit.jupiter.MockitoExtension
|
||||||
|
import org.mockito.kotlin.*
|
||||||
|
import utils.TestTransaction
|
||||||
|
import utils.UserBuilder
|
||||||
|
import java.net.URL
|
||||||
|
|
||||||
|
|
||||||
|
@ExtendWith(MockitoExtension::class)
|
||||||
|
class ApAcceptProcessorTest {
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private lateinit var userQueryService: UserQueryService
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private lateinit var relationshipService: RelationshipService
|
||||||
|
|
||||||
|
@Spy
|
||||||
|
private val transaction = TestTransaction
|
||||||
|
|
||||||
|
@InjectMocks
|
||||||
|
private lateinit var apAcceptProcessor: ApAcceptProcessor
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `internalProcess objectがFollowの場合フォローを承認する`() = runTest {
|
||||||
|
|
||||||
|
val json = """"""
|
||||||
|
val objectMapper = ActivityPubConfig().objectMapper()
|
||||||
|
val jsonNode = objectMapper.readTree(json)
|
||||||
|
|
||||||
|
val accept = Accept(
|
||||||
|
apObject = Follow(
|
||||||
|
apObject = "https://example.com",
|
||||||
|
actor = "https://remote.example.com"
|
||||||
|
),
|
||||||
|
actor = "https://example.com"
|
||||||
|
)
|
||||||
|
val activity = ActivityPubProcessContext<Accept>(
|
||||||
|
accept, jsonNode, HttpRequest(
|
||||||
|
URL("https://example.com"),
|
||||||
|
HttpHeaders(emptyMap()), HttpMethod.POST
|
||||||
|
), null, true
|
||||||
|
)
|
||||||
|
|
||||||
|
val user = UserBuilder.localUserOf()
|
||||||
|
whenever(userQueryService.findByUrl(eq("https://example.com"))).doReturn(user)
|
||||||
|
val remoteUser = UserBuilder.remoteUserOf()
|
||||||
|
whenever(userQueryService.findByUrl(eq("https://remote.example.com"))).doReturn(remoteUser)
|
||||||
|
|
||||||
|
apAcceptProcessor.internalProcess(activity)
|
||||||
|
|
||||||
|
verify(relationshipService, times(1)).acceptFollowRequest(eq(user.id), eq(remoteUser.id), eq(false))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `internalProcess objectがFollow以外の場合IllegalActivityPubObjecExceptionが発生する`() = runTest {
|
||||||
|
val json = """"""
|
||||||
|
val objectMapper = ActivityPubConfig().objectMapper()
|
||||||
|
val jsonNode = objectMapper.readTree(json)
|
||||||
|
|
||||||
|
val accept = Accept(
|
||||||
|
apObject = Like(
|
||||||
|
apObject = "https://example.com",
|
||||||
|
actor = "https://remote.example.com",
|
||||||
|
content = "",
|
||||||
|
id = ""
|
||||||
|
),
|
||||||
|
actor = "https://example.com"
|
||||||
|
)
|
||||||
|
val activity = ActivityPubProcessContext<Accept>(
|
||||||
|
accept, jsonNode, HttpRequest(
|
||||||
|
URL("https://example.com"),
|
||||||
|
HttpHeaders(emptyMap()), HttpMethod.POST
|
||||||
|
), null, true
|
||||||
|
)
|
||||||
|
|
||||||
|
assertThrows<IllegalActivityPubObjectException> {
|
||||||
|
apAcceptProcessor.internalProcess(activity)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `isSupproted Acceptにはtrue`() {
|
||||||
|
val actual = apAcceptProcessor.isSupported(ActivityType.Accept)
|
||||||
|
assertThat(actual).isTrue()
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestFactory
|
||||||
|
fun `isSupported Accept以外にはfalse`(): List<DynamicTest> {
|
||||||
|
return ActivityType
|
||||||
|
.values()
|
||||||
|
.filterNot { it == ActivityType.Accept }
|
||||||
|
.map {
|
||||||
|
dynamicTest("isSupported $it にはfalse") {
|
||||||
|
|
||||||
|
val actual = apAcceptProcessor.isSupported(it)
|
||||||
|
assertThat(actual).isFalse()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `type Acceptのclassjavaが返ってくる`() {
|
||||||
|
assertThat(apAcceptProcessor.type()).isEqualTo(Accept::class.java)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
package dev.usbharu.hideout.activitypub.service.activity.accept
|
||||||
|
|
||||||
|
import dev.usbharu.hideout.activitypub.domain.model.Accept
|
||||||
|
import dev.usbharu.hideout.activitypub.domain.model.Follow
|
||||||
|
import dev.usbharu.hideout.core.external.job.DeliverAcceptJob
|
||||||
|
import dev.usbharu.hideout.core.external.job.DeliverAcceptJobParam
|
||||||
|
import dev.usbharu.hideout.core.service.job.JobQueueParentService
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith
|
||||||
|
import org.mockito.InjectMocks
|
||||||
|
import org.mockito.Mock
|
||||||
|
import org.mockito.junit.jupiter.MockitoExtension
|
||||||
|
import org.mockito.kotlin.eq
|
||||||
|
import org.mockito.kotlin.times
|
||||||
|
import org.mockito.kotlin.verify
|
||||||
|
import utils.UserBuilder
|
||||||
|
|
||||||
|
@ExtendWith(MockitoExtension::class)
|
||||||
|
class ApSendAcceptServiceImplTest {
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private lateinit var jobQueueParentService: JobQueueParentService
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private lateinit var deliverAcceptJob: DeliverAcceptJob
|
||||||
|
|
||||||
|
@InjectMocks
|
||||||
|
private lateinit var apSendAcceptServiceImpl: ApSendAcceptServiceImpl
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `sendAccept DeliverAcceptJobが発行される`() = runTest {
|
||||||
|
val user = UserBuilder.localUserOf()
|
||||||
|
val remoteUser = UserBuilder.remoteUserOf()
|
||||||
|
|
||||||
|
apSendAcceptServiceImpl.sendAcceptFollow(user, remoteUser)
|
||||||
|
|
||||||
|
val deliverAcceptJobParam = DeliverAcceptJobParam(
|
||||||
|
Accept(apObject = Follow(apObject = user.url, actor = remoteUser.url), actor = user.url),
|
||||||
|
remoteUser.inbox,
|
||||||
|
user.id
|
||||||
|
)
|
||||||
|
verify(jobQueueParentService, times(1)).scheduleTypeSafe(eq(deliverAcceptJob), eq(deliverAcceptJobParam))
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,78 @@
|
||||||
|
package dev.usbharu.hideout.activitypub.service.activity.block
|
||||||
|
|
||||||
|
import dev.usbharu.hideout.activitypub.domain.model.Block
|
||||||
|
import dev.usbharu.hideout.activitypub.domain.model.Follow
|
||||||
|
import dev.usbharu.hideout.activitypub.domain.model.Reject
|
||||||
|
import dev.usbharu.hideout.activitypub.service.common.APRequestService
|
||||||
|
import dev.usbharu.hideout.core.domain.model.user.UserRepository
|
||||||
|
import dev.usbharu.hideout.core.external.job.DeliverBlockJob
|
||||||
|
import dev.usbharu.hideout.core.external.job.DeliverBlockJobParam
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
import org.assertj.core.api.Assertions.assertThat
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith
|
||||||
|
import org.mockito.InjectMocks
|
||||||
|
import org.mockito.Mock
|
||||||
|
import org.mockito.Spy
|
||||||
|
import org.mockito.junit.jupiter.MockitoExtension
|
||||||
|
import org.mockito.kotlin.*
|
||||||
|
import utils.TestTransaction
|
||||||
|
import utils.UserBuilder
|
||||||
|
|
||||||
|
@ExtendWith(MockitoExtension::class)
|
||||||
|
class APDeliverBlockJobProcessorTest {
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private lateinit var apRequestService: APRequestService
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private lateinit var userRepository: UserRepository
|
||||||
|
|
||||||
|
@Spy
|
||||||
|
private val transaction = TestTransaction
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private lateinit var deliverBlockJob: DeliverBlockJob
|
||||||
|
|
||||||
|
@InjectMocks
|
||||||
|
private lateinit var apDeliverBlockJobProcessor: APDeliverBlockJobProcessor
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `process rejectとblockがapPostされる`() = runTest {
|
||||||
|
val user = UserBuilder.localUserOf()
|
||||||
|
whenever(userRepository.findById(eq(user.id))).doReturn(user)
|
||||||
|
|
||||||
|
|
||||||
|
val block = Block(
|
||||||
|
actor = user.url,
|
||||||
|
"https://example.com/block",
|
||||||
|
apObject = "https://remote.example.com"
|
||||||
|
)
|
||||||
|
val reject = Reject(
|
||||||
|
actor = user.url,
|
||||||
|
"https://example.com/reject/follow",
|
||||||
|
apObject = Follow(
|
||||||
|
apObject = user.url,
|
||||||
|
actor = "https://remote.example.com"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
val param = DeliverBlockJobParam(
|
||||||
|
user.id,
|
||||||
|
block,
|
||||||
|
reject,
|
||||||
|
"https://remote.example.com"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
apDeliverBlockJobProcessor.process(param)
|
||||||
|
|
||||||
|
verify(apRequestService, times(1)).apPost(eq("https://remote.example.com"), eq(block), eq(user))
|
||||||
|
verify(apRequestService, times(1)).apPost(eq("https://remote.example.com"), eq(reject), eq(user))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `job deliverBlockJobが返ってくる`() {
|
||||||
|
val actual = apDeliverBlockJobProcessor.job()
|
||||||
|
assertThat(actual).isEqualTo(deliverBlockJob)
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,7 +10,6 @@ class ContextSerializerTest {
|
||||||
@Test
|
@Test
|
||||||
fun serialize() {
|
fun serialize() {
|
||||||
val accept = Accept(
|
val accept = Accept(
|
||||||
name = "aaa",
|
|
||||||
actor = "bbb",
|
actor = "bbb",
|
||||||
apObject = Follow(
|
apObject = Follow(
|
||||||
apObject = "ddd",
|
apObject = "ddd",
|
||||||
|
|
|
@ -0,0 +1,786 @@
|
||||||
|
package dev.usbharu.hideout.core.service.relationship
|
||||||
|
|
||||||
|
import dev.usbharu.hideout.activitypub.service.activity.accept.ApSendAcceptService
|
||||||
|
import dev.usbharu.hideout.activitypub.service.activity.block.APSendBlockService
|
||||||
|
import dev.usbharu.hideout.activitypub.service.activity.follow.APSendFollowService
|
||||||
|
import dev.usbharu.hideout.activitypub.service.activity.reject.ApSendRejectService
|
||||||
|
import dev.usbharu.hideout.activitypub.service.activity.undo.APSendUndoService
|
||||||
|
import dev.usbharu.hideout.application.config.ApplicationConfig
|
||||||
|
import dev.usbharu.hideout.core.domain.model.relationship.Relationship
|
||||||
|
import dev.usbharu.hideout.core.domain.model.relationship.RelationshipRepository
|
||||||
|
import dev.usbharu.hideout.core.query.UserQueryService
|
||||||
|
import dev.usbharu.hideout.core.service.follow.SendFollowDto
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.junit.jupiter.api.assertThrows
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith
|
||||||
|
import org.mockito.InjectMocks
|
||||||
|
import org.mockito.Mock
|
||||||
|
import org.mockito.Spy
|
||||||
|
import org.mockito.junit.jupiter.MockitoExtension
|
||||||
|
import org.mockito.kotlin.*
|
||||||
|
import utils.UserBuilder
|
||||||
|
import java.net.URL
|
||||||
|
|
||||||
|
@ExtendWith(MockitoExtension::class)
|
||||||
|
class RelationshipServiceImplTest {
|
||||||
|
|
||||||
|
@Spy
|
||||||
|
private val applicationConfig = ApplicationConfig(URL("https://example.com"))
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private lateinit var userQueryService: UserQueryService
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private lateinit var relationshipRepository: RelationshipRepository
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private lateinit var apSendFollowService: APSendFollowService
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private lateinit var apSendBlockService: APSendBlockService
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private lateinit var apSendAcceptService: ApSendAcceptService
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private lateinit var apSendRejectService: ApSendRejectService
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private lateinit var apSendUndoService: APSendUndoService
|
||||||
|
|
||||||
|
@InjectMocks
|
||||||
|
private lateinit var relationshipServiceImpl: RelationshipServiceImpl
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `followRequest ローカルの場合followRequestフラグがtrueで永続化される`() = runTest {
|
||||||
|
whenever(userQueryService.findById(eq(5678))).doReturn(UserBuilder.localUserOf(domain = "example.com"))
|
||||||
|
|
||||||
|
relationshipServiceImpl.followRequest(1234, 5678)
|
||||||
|
|
||||||
|
verify(relationshipRepository, times(1)).save(
|
||||||
|
eq(
|
||||||
|
Relationship(
|
||||||
|
userId = 1234,
|
||||||
|
targetUserId = 5678,
|
||||||
|
following = false,
|
||||||
|
blocking = false,
|
||||||
|
muting = false,
|
||||||
|
followRequest = true,
|
||||||
|
ignoreFollowRequestFromTarget = false
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `followRequest リモートの場合Followアクティビティが配送される`() = runTest {
|
||||||
|
val localUser = UserBuilder.localUserOf(domain = "example.com")
|
||||||
|
whenever(userQueryService.findById(eq(1234))).doReturn(localUser)
|
||||||
|
val remoteUser = UserBuilder.remoteUserOf(domain = "remote.example.com")
|
||||||
|
whenever(userQueryService.findById(eq(5678))).doReturn(remoteUser)
|
||||||
|
|
||||||
|
relationshipServiceImpl.followRequest(1234, 5678)
|
||||||
|
|
||||||
|
verify(relationshipRepository, times(1)).save(
|
||||||
|
eq(
|
||||||
|
Relationship(
|
||||||
|
userId = 1234,
|
||||||
|
targetUserId = 5678,
|
||||||
|
following = false,
|
||||||
|
blocking = false,
|
||||||
|
muting = false,
|
||||||
|
followRequest = true,
|
||||||
|
ignoreFollowRequestFromTarget = false
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
verify(apSendFollowService, times(1)).sendFollow(eq(SendFollowDto(localUser, remoteUser)))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `followRequest ブロックされている場合フォローリクエスト出来ない`() = runTest {
|
||||||
|
whenever(relationshipRepository.findByUserIdAndTargetUserId(eq(1234), eq(5678))).doReturn(null)
|
||||||
|
whenever(relationshipRepository.findByUserIdAndTargetUserId(eq(5678), eq(1234))).doReturn(
|
||||||
|
Relationship(
|
||||||
|
userId = 5678,
|
||||||
|
targetUserId = 1234,
|
||||||
|
following = false,
|
||||||
|
blocking = true,
|
||||||
|
muting = false,
|
||||||
|
followRequest = false,
|
||||||
|
ignoreFollowRequestFromTarget = false
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
relationshipServiceImpl.followRequest(1234, 5678)
|
||||||
|
|
||||||
|
verify(relationshipRepository, never()).save(any())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `followRequest ブロックしている場合フォローリクエスト出来ない`() = runTest {
|
||||||
|
whenever(relationshipRepository.findByUserIdAndTargetUserId(eq(1234), eq(5678))).doReturn(
|
||||||
|
Relationship(
|
||||||
|
userId = 1234,
|
||||||
|
targetUserId = 5678,
|
||||||
|
following = false,
|
||||||
|
blocking = true,
|
||||||
|
muting = false,
|
||||||
|
followRequest = false,
|
||||||
|
ignoreFollowRequestFromTarget = false
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
relationshipServiceImpl.followRequest(1234, 5678)
|
||||||
|
|
||||||
|
verify(relationshipRepository, never()).save(any())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `followRequest 既にフォローしている場合は念の為フォロー承認を自動で行う`() = runTest {
|
||||||
|
val remoteUser = UserBuilder.remoteUserOf(domain = "remote.example.com")
|
||||||
|
whenever(userQueryService.findById(eq(1234))).doReturn(remoteUser)
|
||||||
|
val localUser = UserBuilder.localUserOf(domain = "example.com")
|
||||||
|
whenever(userQueryService.findById(eq(5678))).doReturn(localUser)
|
||||||
|
whenever(relationshipRepository.findByUserIdAndTargetUserId(eq(1234), eq(5678))).doReturn(
|
||||||
|
Relationship(
|
||||||
|
userId = 1234,
|
||||||
|
targetUserId = 5678,
|
||||||
|
following = true,
|
||||||
|
blocking = false,
|
||||||
|
muting = false,
|
||||||
|
followRequest = false,
|
||||||
|
ignoreFollowRequestFromTarget = false
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
relationshipServiceImpl.followRequest(1234, 5678)
|
||||||
|
|
||||||
|
verify(relationshipRepository, times(1)).save(
|
||||||
|
eq(
|
||||||
|
Relationship(
|
||||||
|
userId = 1234,
|
||||||
|
targetUserId = 5678,
|
||||||
|
following = true,
|
||||||
|
blocking = false,
|
||||||
|
muting = false,
|
||||||
|
followRequest = false,
|
||||||
|
ignoreFollowRequestFromTarget = false
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
verify(apSendAcceptService, times(1)).sendAcceptFollow(eq(localUser), eq(remoteUser))
|
||||||
|
verify(apSendFollowService, never()).sendFollow(any())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `followRequest フォローリクエスト無視の場合は無視する`() = runTest {
|
||||||
|
whenever(relationshipRepository.findByUserIdAndTargetUserId(eq(1234), eq(5678))).doReturn(
|
||||||
|
Relationship(
|
||||||
|
userId = 1234,
|
||||||
|
targetUserId = 5678,
|
||||||
|
following = false,
|
||||||
|
blocking = false,
|
||||||
|
muting = false,
|
||||||
|
followRequest = false,
|
||||||
|
ignoreFollowRequestFromTarget = false
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
whenever(relationshipRepository.findByUserIdAndTargetUserId(eq(5678), eq(1234))).doReturn(
|
||||||
|
Relationship(
|
||||||
|
userId = 5678,
|
||||||
|
targetUserId = 1234,
|
||||||
|
following = false,
|
||||||
|
blocking = false,
|
||||||
|
muting = false,
|
||||||
|
followRequest = false,
|
||||||
|
ignoreFollowRequestFromTarget = true
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
relationshipServiceImpl.followRequest(1234, 5678)
|
||||||
|
|
||||||
|
verify(relationshipRepository, never()).save(any())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `block ローカルユーザーの場合永続化される`() = runTest {
|
||||||
|
whenever(userQueryService.findById(eq(5678))).doReturn(UserBuilder.localUserOf(domain = "example.com"))
|
||||||
|
|
||||||
|
relationshipServiceImpl.block(1234, 5678)
|
||||||
|
|
||||||
|
verify(relationshipRepository, times(1)).save(
|
||||||
|
eq(
|
||||||
|
Relationship(
|
||||||
|
userId = 1234,
|
||||||
|
targetUserId = 5678,
|
||||||
|
following = false,
|
||||||
|
blocking = true,
|
||||||
|
muting = false,
|
||||||
|
followRequest = false,
|
||||||
|
ignoreFollowRequestFromTarget = false
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `block リモートユーザーの場合永続化されて配送される`() = runTest {
|
||||||
|
val localUser = UserBuilder.localUserOf(domain = "example.com")
|
||||||
|
whenever(userQueryService.findById(eq(1234))).doReturn(localUser)
|
||||||
|
val remoteUser = UserBuilder.remoteUserOf(domain = "remote.example.com")
|
||||||
|
whenever(userQueryService.findById(eq(5678))).doReturn(remoteUser)
|
||||||
|
|
||||||
|
relationshipServiceImpl.block(1234, 5678)
|
||||||
|
|
||||||
|
verify(relationshipRepository, times(1)).save(
|
||||||
|
eq(
|
||||||
|
Relationship(
|
||||||
|
userId = 1234,
|
||||||
|
targetUserId = 5678,
|
||||||
|
following = false,
|
||||||
|
blocking = true,
|
||||||
|
muting = false,
|
||||||
|
followRequest = false,
|
||||||
|
ignoreFollowRequestFromTarget = false
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
verify(apSendBlockService, times(1)).sendBlock(eq(localUser), eq(remoteUser))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `acceptFollowRequest ローカルユーザーの場合永続化される`() = runTest {
|
||||||
|
whenever(userQueryService.findById(eq(5678))).doReturn(UserBuilder.localUserOf(domain = "example.com"))
|
||||||
|
|
||||||
|
whenever(relationshipRepository.findByUserIdAndTargetUserId(eq(5678), eq(1234))).doReturn(
|
||||||
|
Relationship(
|
||||||
|
userId = 5678,
|
||||||
|
targetUserId = 1234,
|
||||||
|
following = false,
|
||||||
|
blocking = false,
|
||||||
|
muting = false,
|
||||||
|
followRequest = true,
|
||||||
|
ignoreFollowRequestFromTarget = false
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
relationshipServiceImpl.acceptFollowRequest(1234, 5678, false)
|
||||||
|
|
||||||
|
verify(relationshipRepository, times(1)).save(
|
||||||
|
Relationship(
|
||||||
|
userId = 5678,
|
||||||
|
targetUserId = 1234,
|
||||||
|
following = true,
|
||||||
|
blocking = false,
|
||||||
|
muting = false,
|
||||||
|
followRequest = false,
|
||||||
|
ignoreFollowRequestFromTarget = false
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
verify(apSendAcceptService, never()).sendAcceptFollow(any(), any())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `acceptFollowRequest リモートユーザーの場合永続化されて配送される`() = runTest {
|
||||||
|
val localUser = UserBuilder.localUserOf(domain = "example.com")
|
||||||
|
whenever(userQueryService.findById(eq(1234))).doReturn(localUser)
|
||||||
|
val remoteUser = UserBuilder.remoteUserOf(domain = "remote.example.com")
|
||||||
|
whenever(userQueryService.findById(eq(5678))).doReturn(remoteUser)
|
||||||
|
|
||||||
|
whenever(relationshipRepository.findByUserIdAndTargetUserId(eq(5678), eq(1234))).doReturn(
|
||||||
|
Relationship(
|
||||||
|
userId = 5678,
|
||||||
|
targetUserId = 1234,
|
||||||
|
following = false,
|
||||||
|
blocking = false,
|
||||||
|
muting = false,
|
||||||
|
followRequest = true,
|
||||||
|
ignoreFollowRequestFromTarget = false
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
relationshipServiceImpl.acceptFollowRequest(1234, 5678, false)
|
||||||
|
|
||||||
|
verify(relationshipRepository, times(1)).save(
|
||||||
|
eq(
|
||||||
|
Relationship(
|
||||||
|
userId = 5678,
|
||||||
|
targetUserId = 1234,
|
||||||
|
following = true,
|
||||||
|
blocking = false,
|
||||||
|
muting = false,
|
||||||
|
followRequest = false,
|
||||||
|
ignoreFollowRequestFromTarget = false
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
verify(apSendAcceptService, times(1)).sendAcceptFollow(eq(localUser), eq(remoteUser))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `acceptFollowRequest Relationshipが存在しないときは何もしない`() = runTest {
|
||||||
|
relationshipServiceImpl.acceptFollowRequest(1234, 5678, false)
|
||||||
|
|
||||||
|
verify(apSendAcceptService, never()).sendAcceptFollow(any(), any())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `acceptFollowRequest フォローリクエストが存在せずforceがfalseのとき何もしない`() = runTest {
|
||||||
|
whenever(relationshipRepository.findByUserIdAndTargetUserId(eq(5678), eq(1234))).doReturn(
|
||||||
|
Relationship(
|
||||||
|
5678, 1234, false, false, false, false, false
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
relationshipServiceImpl.acceptFollowRequest(1234, 5678, false)
|
||||||
|
|
||||||
|
verify(apSendAcceptService, never()).sendAcceptFollow(any(), any())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `acceptFollowRequest フォローリクエストが存在せずforceがtrueのときフォローを承認する`() = runTest {
|
||||||
|
whenever(userQueryService.findById(eq(5678))).doReturn(UserBuilder.remoteUserOf(domain = "remote.example.com"))
|
||||||
|
|
||||||
|
whenever(relationshipRepository.findByUserIdAndTargetUserId(eq(5678), eq(1234))).doReturn(
|
||||||
|
Relationship(
|
||||||
|
5678, 1234, false, false, false, false, false
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
relationshipServiceImpl.acceptFollowRequest(1234, 5678, true)
|
||||||
|
|
||||||
|
verify(relationshipRepository, times(1)).save(
|
||||||
|
eq(
|
||||||
|
Relationship(
|
||||||
|
userId = 5678,
|
||||||
|
targetUserId = 1234,
|
||||||
|
following = true,
|
||||||
|
blocking = false,
|
||||||
|
muting = false,
|
||||||
|
followRequest = false,
|
||||||
|
ignoreFollowRequestFromTarget = false
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `acceptFollowRequest ブロックしている場合は何もしない`() = runTest {
|
||||||
|
whenever(relationshipRepository.findByUserIdAndTargetUserId(eq(5678), eq(1234))).doReturn(
|
||||||
|
Relationship(
|
||||||
|
5678, 1234, false, true, false, true, false
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
assertThrows<IllegalStateException> {
|
||||||
|
relationshipServiceImpl.acceptFollowRequest(1234, 5678, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
verify(relationshipRepository, never()).save(any())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `acceptFollowRequest ブロックされている場合は何もしない`() = runTest {
|
||||||
|
whenever(relationshipRepository.findByUserIdAndTargetUserId(eq(1234), eq(5678))).doReturn(
|
||||||
|
Relationship(
|
||||||
|
1234, 5678, false, false, false, true, false
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
whenever(relationshipRepository.findByUserIdAndTargetUserId(eq(5678), eq(1234))).doReturn(
|
||||||
|
Relationship(
|
||||||
|
5678, 1234, false, true, false, true, false
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
assertThrows<IllegalStateException> {
|
||||||
|
relationshipServiceImpl.acceptFollowRequest(1234, 5678, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
verify(relationshipRepository, never()).save(any())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `rejectFollowRequest ローカルユーザーの場合永続化される`() = runTest {
|
||||||
|
whenever(userQueryService.findById(eq(5678))).doReturn(UserBuilder.localUserOf(domain = "example.com"))
|
||||||
|
|
||||||
|
whenever(relationshipRepository.findByUserIdAndTargetUserId(eq(5678), eq(1234))).doReturn(
|
||||||
|
Relationship(
|
||||||
|
userId = 5678,
|
||||||
|
targetUserId = 1234,
|
||||||
|
following = false,
|
||||||
|
blocking = false,
|
||||||
|
muting = false,
|
||||||
|
followRequest = true,
|
||||||
|
ignoreFollowRequestFromTarget = false
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
relationshipServiceImpl.rejectFollowRequest(1234, 5678)
|
||||||
|
|
||||||
|
verify(relationshipRepository, times(1)).save(
|
||||||
|
eq(
|
||||||
|
Relationship(
|
||||||
|
userId = 5678,
|
||||||
|
targetUserId = 1234,
|
||||||
|
following = false,
|
||||||
|
blocking = false,
|
||||||
|
muting = false,
|
||||||
|
followRequest = false,
|
||||||
|
ignoreFollowRequestFromTarget = false
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
verify(apSendRejectService, never()).sendRejectFollow(any(), any())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `rejectFollowRequest リモートユーザーの場合永続化されて配送される`() = runTest {
|
||||||
|
val localUser = UserBuilder.localUserOf(domain = "example.com")
|
||||||
|
whenever(userQueryService.findById(eq(1234))).doReturn(localUser)
|
||||||
|
|
||||||
|
val remoteUser = UserBuilder.remoteUserOf(domain = "remote.example.com")
|
||||||
|
whenever(userQueryService.findById(eq(5678))).doReturn(remoteUser)
|
||||||
|
|
||||||
|
whenever(relationshipRepository.findByUserIdAndTargetUserId(eq(5678), eq(1234))).doReturn(
|
||||||
|
Relationship(
|
||||||
|
userId = 5678,
|
||||||
|
targetUserId = 1234,
|
||||||
|
following = false,
|
||||||
|
blocking = false,
|
||||||
|
muting = false,
|
||||||
|
followRequest = true,
|
||||||
|
ignoreFollowRequestFromTarget = false
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
relationshipServiceImpl.rejectFollowRequest(1234, 5678)
|
||||||
|
|
||||||
|
verify(relationshipRepository, times(1)).save(
|
||||||
|
eq(
|
||||||
|
Relationship(
|
||||||
|
userId = 5678,
|
||||||
|
targetUserId = 1234,
|
||||||
|
following = false,
|
||||||
|
blocking = false,
|
||||||
|
muting = false,
|
||||||
|
followRequest = false,
|
||||||
|
ignoreFollowRequestFromTarget = false
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
verify(apSendRejectService, times(1)).sendRejectFollow(eq(localUser), eq(remoteUser))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `rejectFollowRequest Relationshipが存在しないとき何もしない`() = runTest {
|
||||||
|
|
||||||
|
relationshipServiceImpl.rejectFollowRequest(1234, 5678)
|
||||||
|
|
||||||
|
verify(relationshipRepository, never()).save(any())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `rejectFollowRequest フォローリクエストが存在しない場合何もしない`() = runTest {
|
||||||
|
whenever(relationshipRepository.findByUserIdAndTargetUserId(eq(5678), eq(1234))).doReturn(
|
||||||
|
Relationship(
|
||||||
|
userId = 5678,
|
||||||
|
targetUserId = 1234,
|
||||||
|
following = false,
|
||||||
|
blocking = false,
|
||||||
|
muting = false,
|
||||||
|
followRequest = false,
|
||||||
|
ignoreFollowRequestFromTarget = false
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
relationshipServiceImpl.rejectFollowRequest(1234, 5678)
|
||||||
|
|
||||||
|
verify(relationshipRepository, never()).save(any())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `ignoreFollowRequest 永続化される`() = runTest {
|
||||||
|
relationshipServiceImpl.ignoreFollowRequest(1234, 5678)
|
||||||
|
|
||||||
|
verify(relationshipRepository, times(1)).save(
|
||||||
|
eq(
|
||||||
|
Relationship(
|
||||||
|
userId = 1234,
|
||||||
|
targetUserId = 5678,
|
||||||
|
following = false,
|
||||||
|
blocking = false,
|
||||||
|
muting = false,
|
||||||
|
followRequest = false,
|
||||||
|
ignoreFollowRequestFromTarget = true
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `unfollow ローカルユーザーの場合永続化される`() = runTest {
|
||||||
|
whenever(userQueryService.findById(eq(5678))).doReturn(UserBuilder.localUserOf(domain = "example.com"))
|
||||||
|
whenever(relationshipRepository.findByUserIdAndTargetUserId(eq(1234), eq(5678))).doReturn(
|
||||||
|
Relationship(
|
||||||
|
userId = 1234,
|
||||||
|
targetUserId = 5678,
|
||||||
|
following = true,
|
||||||
|
blocking = false,
|
||||||
|
muting = false,
|
||||||
|
followRequest = false,
|
||||||
|
ignoreFollowRequestFromTarget = false
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
relationshipServiceImpl.unfollow(1234, 5678)
|
||||||
|
|
||||||
|
verify(relationshipRepository, times(1)).save(
|
||||||
|
eq(
|
||||||
|
Relationship(
|
||||||
|
userId = 1234,
|
||||||
|
targetUserId = 5678,
|
||||||
|
following = false,
|
||||||
|
blocking = false,
|
||||||
|
muting = false,
|
||||||
|
followRequest = false,
|
||||||
|
ignoreFollowRequestFromTarget = false
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
verify(apSendUndoService, never()).sendUndoFollow(any(), any())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `unfollow リモートユーザー場合永続化されて配送される`() = runTest {
|
||||||
|
val localUser = UserBuilder.localUserOf(domain = "example.com")
|
||||||
|
whenever(userQueryService.findById(eq(1234))).doReturn(localUser)
|
||||||
|
|
||||||
|
val remoteUser = UserBuilder.remoteUserOf(domain = "remote.example.com")
|
||||||
|
whenever(userQueryService.findById(eq(5678))).doReturn(remoteUser)
|
||||||
|
|
||||||
|
whenever(relationshipRepository.findByUserIdAndTargetUserId(eq(1234), eq(5678))).doReturn(
|
||||||
|
Relationship(
|
||||||
|
userId = 1234,
|
||||||
|
targetUserId = 5678,
|
||||||
|
following = true,
|
||||||
|
blocking = false,
|
||||||
|
muting = false,
|
||||||
|
followRequest = false,
|
||||||
|
ignoreFollowRequestFromTarget = false
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
relationshipServiceImpl.unfollow(1234, 5678)
|
||||||
|
|
||||||
|
verify(relationshipRepository, times(1)).save(
|
||||||
|
eq(
|
||||||
|
Relationship(
|
||||||
|
userId = 1234,
|
||||||
|
targetUserId = 5678,
|
||||||
|
following = false,
|
||||||
|
blocking = false,
|
||||||
|
muting = false,
|
||||||
|
followRequest = false,
|
||||||
|
ignoreFollowRequestFromTarget = false
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
verify(apSendUndoService, times(1)).sendUndoFollow(eq(localUser), eq(remoteUser))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `unfollow Relationshipが存在しないときは何もしない`() = runTest {
|
||||||
|
relationshipServiceImpl.unfollow(1234, 5678)
|
||||||
|
|
||||||
|
verify(relationshipRepository, never()).save(any())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `unfollow フォローしていなかった場合は何もしない`() = runTest {
|
||||||
|
whenever(relationshipRepository.findByUserIdAndTargetUserId(eq(1234), eq(5678))).doReturn(
|
||||||
|
Relationship(
|
||||||
|
userId = 1234,
|
||||||
|
targetUserId = 5678,
|
||||||
|
following = false,
|
||||||
|
blocking = false,
|
||||||
|
muting = false,
|
||||||
|
followRequest = false,
|
||||||
|
ignoreFollowRequestFromTarget = false
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
relationshipServiceImpl.unfollow(1234, 5678)
|
||||||
|
|
||||||
|
verify(relationshipRepository, never()).save(any())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `unblock ローカルユーザーの場合永続化される`() = runTest {
|
||||||
|
whenever(userQueryService.findById(eq(5678))).doReturn(UserBuilder.localUserOf(domain = "example.com"))
|
||||||
|
whenever(relationshipRepository.findByUserIdAndTargetUserId(eq(1234), eq(5678))).doReturn(
|
||||||
|
Relationship(
|
||||||
|
userId = 1234,
|
||||||
|
targetUserId = 5678,
|
||||||
|
following = false,
|
||||||
|
blocking = true,
|
||||||
|
muting = false,
|
||||||
|
followRequest = false,
|
||||||
|
ignoreFollowRequestFromTarget = false
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
relationshipServiceImpl.unblock(1234, 5678)
|
||||||
|
|
||||||
|
verify(relationshipRepository, times(1)).save(
|
||||||
|
eq(
|
||||||
|
Relationship(
|
||||||
|
userId = 1234,
|
||||||
|
targetUserId = 5678,
|
||||||
|
following = false,
|
||||||
|
blocking = false,
|
||||||
|
muting = false,
|
||||||
|
followRequest = false,
|
||||||
|
ignoreFollowRequestFromTarget = false
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
verify(apSendUndoService, never()).sendUndoBlock(any(), any())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `unblock リモートユーザーの場合永続化されて配送される`() = runTest {
|
||||||
|
val localUser = UserBuilder.localUserOf(domain = "example.com")
|
||||||
|
whenever(userQueryService.findById(eq(1234))).doReturn(localUser)
|
||||||
|
|
||||||
|
val remoteUser = UserBuilder.remoteUserOf(domain = "remote.example.com")
|
||||||
|
whenever(userQueryService.findById(eq(5678))).doReturn(remoteUser)
|
||||||
|
|
||||||
|
whenever(relationshipRepository.findByUserIdAndTargetUserId(eq(1234), eq(5678))).doReturn(
|
||||||
|
Relationship(
|
||||||
|
userId = 1234,
|
||||||
|
targetUserId = 5678,
|
||||||
|
following = false,
|
||||||
|
blocking = true,
|
||||||
|
muting = false,
|
||||||
|
followRequest = false,
|
||||||
|
ignoreFollowRequestFromTarget = false
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
relationshipServiceImpl.unblock(1234, 5678)
|
||||||
|
|
||||||
|
verify(relationshipRepository, times(1)).save(
|
||||||
|
eq(
|
||||||
|
Relationship(
|
||||||
|
1234,
|
||||||
|
5678,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
verify(apSendUndoService, times(1)).sendUndoBlock(eq(localUser), eq(remoteUser))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `unblock Relationshipがない場合何もしない`() = runTest {
|
||||||
|
relationshipServiceImpl.unblock(1234, 5678)
|
||||||
|
|
||||||
|
verify(relationshipRepository, never()).save(any())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `unblock ブロックしていない場合は何もしない`() = runTest {
|
||||||
|
whenever(relationshipRepository.findByUserIdAndTargetUserId(eq(1234), eq(5678))).doReturn(
|
||||||
|
Relationship(
|
||||||
|
userId = 1234,
|
||||||
|
targetUserId = 5678,
|
||||||
|
following = false,
|
||||||
|
blocking = false,
|
||||||
|
muting = false,
|
||||||
|
followRequest = false,
|
||||||
|
ignoreFollowRequestFromTarget = false
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
relationshipServiceImpl.unblock(1234, 5678)
|
||||||
|
|
||||||
|
verify(relationshipRepository, never()).save(any())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `mute ミュートが永続化される`() = runTest {
|
||||||
|
relationshipServiceImpl.mute(1234, 5678)
|
||||||
|
|
||||||
|
verify(relationshipRepository, times(1)).save(
|
||||||
|
eq(
|
||||||
|
Relationship(
|
||||||
|
userId = 1234,
|
||||||
|
targetUserId = 5678,
|
||||||
|
following = false,
|
||||||
|
blocking = false,
|
||||||
|
muting = true,
|
||||||
|
followRequest = false,
|
||||||
|
ignoreFollowRequestFromTarget = false
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `unmute 永続化される`() = runTest {
|
||||||
|
|
||||||
|
whenever(relationshipRepository.findByUserIdAndTargetUserId(eq(1234), eq(5678))).doReturn(
|
||||||
|
Relationship(
|
||||||
|
userId = 1234,
|
||||||
|
targetUserId = 5678,
|
||||||
|
following = false,
|
||||||
|
blocking = false,
|
||||||
|
muting = true,
|
||||||
|
followRequest = false,
|
||||||
|
ignoreFollowRequestFromTarget = false
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
relationshipServiceImpl.unmute(1234, 5678)
|
||||||
|
|
||||||
|
verify(relationshipRepository, times(1)).save(
|
||||||
|
eq(
|
||||||
|
Relationship(
|
||||||
|
userId = 1234,
|
||||||
|
targetUserId = 5678,
|
||||||
|
following = false,
|
||||||
|
blocking = false,
|
||||||
|
muting = false,
|
||||||
|
followRequest = false,
|
||||||
|
ignoreFollowRequestFromTarget = false
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `unmute Relationshipが存在しない場合は何もしない`() = runTest {
|
||||||
|
relationshipServiceImpl.unmute(1234, 5678)
|
||||||
|
|
||||||
|
verify(relationshipRepository, never()).save(any())
|
||||||
|
}
|
||||||
|
}
|
|
@ -37,8 +37,6 @@ class UserServiceTest {
|
||||||
userRepository,
|
userRepository,
|
||||||
userAuthService,
|
userAuthService,
|
||||||
mock(),
|
mock(),
|
||||||
mock(),
|
|
||||||
mock(),
|
|
||||||
userBuilder,
|
userBuilder,
|
||||||
testApplicationConfig,
|
testApplicationConfig,
|
||||||
mock()
|
mock()
|
||||||
|
@ -68,7 +66,7 @@ class UserServiceTest {
|
||||||
onBlocking { nextId() } doReturn 113345L
|
onBlocking { nextId() } doReturn 113345L
|
||||||
}
|
}
|
||||||
val userService =
|
val userService =
|
||||||
UserServiceImpl(userRepository, mock(), mock(), mock(), mock(), userBuilder, testApplicationConfig, mock())
|
UserServiceImpl(userRepository, mock(), mock(), userBuilder, testApplicationConfig, mock())
|
||||||
val user = RemoteUserCreateDto(
|
val user = RemoteUserCreateDto(
|
||||||
name = "test",
|
name = "test",
|
||||||
domain = "remote.example.com",
|
domain = "remote.example.com",
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
package dev.usbharu.hideout.mastodon.service.account
|
package dev.usbharu.hideout.mastodon.service.account
|
||||||
|
|
||||||
import dev.usbharu.hideout.application.external.Transaction
|
import dev.usbharu.hideout.application.external.Transaction
|
||||||
|
import dev.usbharu.hideout.core.domain.model.relationship.RelationshipRepository
|
||||||
import dev.usbharu.hideout.core.domain.model.user.UserRepository
|
import dev.usbharu.hideout.core.domain.model.user.UserRepository
|
||||||
import dev.usbharu.hideout.core.query.FollowerQueryService
|
import dev.usbharu.hideout.core.query.FollowerQueryService
|
||||||
|
import dev.usbharu.hideout.core.service.relationship.RelationshipService
|
||||||
import dev.usbharu.hideout.core.service.user.UserService
|
import dev.usbharu.hideout.core.service.user.UserService
|
||||||
import dev.usbharu.hideout.domain.mastodon.model.generated.Account
|
import dev.usbharu.hideout.domain.mastodon.model.generated.Account
|
||||||
import dev.usbharu.hideout.domain.mastodon.model.generated.Relationship
|
import dev.usbharu.hideout.domain.mastodon.model.generated.Relationship
|
||||||
|
@ -40,6 +42,12 @@ class AccountApiServiceImplTest {
|
||||||
@Spy
|
@Spy
|
||||||
private val transaction: Transaction = TestTransaction
|
private val transaction: Transaction = TestTransaction
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private lateinit var relationshipService: RelationshipService
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private lateinit var relationshipRepository: RelationshipRepository
|
||||||
|
|
||||||
@InjectMocks
|
@InjectMocks
|
||||||
private lateinit var accountApiServiceImpl: AccountApiServiceImpl
|
private lateinit var accountApiServiceImpl: AccountApiServiceImpl
|
||||||
|
|
||||||
|
@ -157,9 +165,6 @@ class AccountApiServiceImplTest {
|
||||||
)
|
)
|
||||||
).doReturn(statusList)
|
).doReturn(statusList)
|
||||||
|
|
||||||
whenever(followerQueryService.alreadyFollow(eq(userId), eq(loginUser))).doReturn(false)
|
|
||||||
|
|
||||||
|
|
||||||
val accountsStatuses = accountApiServiceImpl.accountsStatuses(
|
val accountsStatuses = accountApiServiceImpl.accountsStatuses(
|
||||||
userid = userId,
|
userid = userId,
|
||||||
maxId = null,
|
maxId = null,
|
||||||
|
@ -197,7 +202,17 @@ class AccountApiServiceImplTest {
|
||||||
)
|
)
|
||||||
).doReturn(statusList)
|
).doReturn(statusList)
|
||||||
|
|
||||||
whenever(followerQueryService.alreadyFollow(eq(userId), eq(loginUser))).doReturn(true)
|
whenever(relationshipRepository.findByUserIdAndTargetUserId(eq(loginUser), eq(userId))).doReturn(
|
||||||
|
dev.usbharu.hideout.core.domain.model.relationship.Relationship(
|
||||||
|
userId = loginUser,
|
||||||
|
targetUserId = userId,
|
||||||
|
following = true,
|
||||||
|
blocking = false,
|
||||||
|
muting = false,
|
||||||
|
followRequest = false,
|
||||||
|
ignoreFollowRequestFromTarget = false
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
val accountsStatuses = accountApiServiceImpl.accountsStatuses(
|
val accountsStatuses = accountApiServiceImpl.accountsStatuses(
|
||||||
|
@ -217,51 +232,34 @@ class AccountApiServiceImplTest {
|
||||||
assertThat(accountsStatuses).hasSize(1)
|
assertThat(accountsStatuses).hasSize(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `follow 既にフォローしている場合は何もしない`() = runTest {
|
|
||||||
val userId = 1234L
|
|
||||||
val followeeId = 1L
|
|
||||||
|
|
||||||
whenever(followerQueryService.alreadyFollow(eq(followeeId), eq(userId))).doReturn(true)
|
|
||||||
|
|
||||||
whenever(followerQueryService.alreadyFollow(eq(userId), eq(followeeId))).doReturn(true)
|
|
||||||
|
|
||||||
whenever(userRepository.findFollowRequestsById(eq(followeeId), eq(userId))).doReturn(false)
|
|
||||||
|
|
||||||
val follow = accountApiServiceImpl.follow(userId, followeeId)
|
|
||||||
|
|
||||||
val expected = Relationship(
|
|
||||||
id = followeeId.toString(),
|
|
||||||
following = true,
|
|
||||||
showingReblogs = true,
|
|
||||||
notifying = false,
|
|
||||||
followedBy = true,
|
|
||||||
blocking = false,
|
|
||||||
blockedBy = false,
|
|
||||||
muting = false,
|
|
||||||
mutingNotifications = false,
|
|
||||||
requested = false,
|
|
||||||
domainBlocking = false,
|
|
||||||
endorsed = false,
|
|
||||||
note = ""
|
|
||||||
)
|
|
||||||
assertThat(follow).isEqualTo(expected)
|
|
||||||
|
|
||||||
verify(userService, never()).followRequest(any(), any())
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `follow 未フォローの場合フォローリクエストが発生する`() = runTest {
|
fun `follow 未フォローの場合フォローリクエストが発生する`() = runTest {
|
||||||
val userId = 1234L
|
val userId = 1234L
|
||||||
val followeeId = 1L
|
val followeeId = 1L
|
||||||
|
|
||||||
whenever(followerQueryService.alreadyFollow(eq(followeeId), eq(userId))).doReturn(false)
|
whenever(relationshipRepository.findByUserIdAndTargetUserId(eq(followeeId), eq(userId))).doReturn(
|
||||||
|
dev.usbharu.hideout.core.domain.model.relationship.Relationship(
|
||||||
|
userId = followeeId,
|
||||||
|
targetUserId = userId,
|
||||||
|
following = true,
|
||||||
|
blocking = false,
|
||||||
|
muting = false,
|
||||||
|
followRequest = false,
|
||||||
|
ignoreFollowRequestFromTarget = false
|
||||||
|
)
|
||||||
|
)
|
||||||
|
whenever(relationshipRepository.findByUserIdAndTargetUserId(eq(userId), eq(followeeId))).doReturn(
|
||||||
|
dev.usbharu.hideout.core.domain.model.relationship.Relationship(
|
||||||
|
userId = userId,
|
||||||
|
targetUserId = followeeId,
|
||||||
|
following = true,
|
||||||
|
blocking = false,
|
||||||
|
muting = false,
|
||||||
|
followRequest = false,
|
||||||
|
ignoreFollowRequestFromTarget = false
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
whenever(userService.followRequest(eq(followeeId), eq(userId))).doReturn(true)
|
|
||||||
|
|
||||||
whenever(followerQueryService.alreadyFollow(eq(userId), eq(followeeId))).doReturn(true)
|
|
||||||
|
|
||||||
whenever(userRepository.findFollowRequestsById(eq(followeeId), eq(userId))).doReturn(false)
|
|
||||||
|
|
||||||
val follow = accountApiServiceImpl.follow(userId, followeeId)
|
val follow = accountApiServiceImpl.follow(userId, followeeId)
|
||||||
|
|
||||||
|
@ -282,14 +280,11 @@ class AccountApiServiceImplTest {
|
||||||
)
|
)
|
||||||
assertThat(follow).isEqualTo(expected)
|
assertThat(follow).isEqualTo(expected)
|
||||||
|
|
||||||
verify(userService, times(1)).followRequest(eq(followeeId), eq(userId))
|
verify(relationshipService, times(1)).followRequest(eq(userId), eq(followeeId))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `relationships idが長すぎたら省略する`() = runTest {
|
fun `relationships idが長すぎたら省略する`() = runTest {
|
||||||
whenever(followerQueryService.alreadyFollow(any(), any())).doReturn(true)
|
|
||||||
|
|
||||||
whenever(userRepository.findFollowRequestsById(any(), any())).doReturn(true)
|
|
||||||
|
|
||||||
val relationships = accountApiServiceImpl.relationships(
|
val relationships = accountApiServiceImpl.relationships(
|
||||||
userid = 1234L,
|
userid = 1234L,
|
||||||
|
@ -297,7 +292,7 @@ class AccountApiServiceImplTest {
|
||||||
withSuspended = false
|
withSuspended = false
|
||||||
)
|
)
|
||||||
|
|
||||||
assertThat(relationships).hasSizeLessThanOrEqualTo(20)
|
assertThat(relationships).hasSize(20)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -310,14 +305,10 @@ class AccountApiServiceImplTest {
|
||||||
|
|
||||||
assertThat(relationships).hasSize(0)
|
assertThat(relationships).hasSize(0)
|
||||||
verify(followerQueryService, never()).alreadyFollow(any(), any())
|
verify(followerQueryService, never()).alreadyFollow(any(), any())
|
||||||
verify(userRepository, never()).findFollowRequestsById(any(), any())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `relationships idに指定されたアカウントの関係を取得する`() = runTest {
|
fun `relationships idに指定されたアカウントの関係を取得する`() = runTest {
|
||||||
whenever(followerQueryService.alreadyFollow(any(), any())).doReturn(true)
|
|
||||||
|
|
||||||
whenever(userRepository.findFollowRequestsById(any(), any())).doReturn(true)
|
|
||||||
|
|
||||||
val relationships = accountApiServiceImpl.relationships(
|
val relationships = accountApiServiceImpl.relationships(
|
||||||
userid = 1234L,
|
userid = 1234L,
|
||||||
|
|
Loading…
Reference in New Issue