mirror of https://github.com/usbharu/Hideout.git
commit
e0368ab7c5
|
@ -6,9 +6,9 @@ open class Person
|
|||
@Suppress("LongParameterList")
|
||||
constructor(
|
||||
type: List<String> = emptyList(),
|
||||
override val name: String,
|
||||
val name: String?,
|
||||
override val id: String,
|
||||
var preferredUsername: String?,
|
||||
var preferredUsername: String,
|
||||
var summary: String?,
|
||||
var inbox: String,
|
||||
var outbox: String,
|
||||
|
@ -19,7 +19,7 @@ constructor(
|
|||
var followers: String?,
|
||||
var following: String?,
|
||||
val manuallyApprovesFollowers: Boolean? = false
|
||||
) : Object(add(type, "Person")), HasId, HasName {
|
||||
) : Object(add(type, "Person")), HasId {
|
||||
|
||||
@Suppress("CyclomaticComplexMethod", "CognitiveComplexMethod")
|
||||
override fun equals(other: Any?): Boolean {
|
||||
|
|
|
@ -3,37 +3,50 @@ package dev.usbharu.hideout.activitypub.service.activity.delete
|
|||
import dev.usbharu.hideout.activitypub.domain.exception.IllegalActivityPubObjectException
|
||||
import dev.usbharu.hideout.activitypub.domain.model.Delete
|
||||
import dev.usbharu.hideout.activitypub.domain.model.HasId
|
||||
import dev.usbharu.hideout.activitypub.domain.model.objects.ObjectValue
|
||||
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.domain.exception.FailedToGetResourcesException
|
||||
import dev.usbharu.hideout.core.domain.model.post.PostRepository
|
||||
import dev.usbharu.hideout.core.query.ActorQueryService
|
||||
import dev.usbharu.hideout.core.query.PostQueryService
|
||||
import dev.usbharu.hideout.core.service.post.PostService
|
||||
import dev.usbharu.hideout.core.service.user.UserService
|
||||
import org.springframework.stereotype.Service
|
||||
|
||||
@Service
|
||||
class APDeleteProcessor(
|
||||
transaction: Transaction,
|
||||
private val postQueryService: PostQueryService,
|
||||
private val postRepository: PostRepository
|
||||
private val actorQueryService: ActorQueryService,
|
||||
private val userService: UserService,
|
||||
private val postService: PostService
|
||||
) :
|
||||
AbstractActivityPubProcessor<Delete>(transaction) {
|
||||
override suspend fun internalProcess(activity: ActivityPubProcessContext<Delete>) {
|
||||
val value = activity.activity.apObject
|
||||
if (value !is HasId) {
|
||||
throw IllegalActivityPubObjectException("object hasn't id")
|
||||
val deleteId = if (value is HasId) {
|
||||
value.id
|
||||
} else if (value is ObjectValue) {
|
||||
value.`object`
|
||||
} else {
|
||||
throw IllegalActivityPubObjectException("object hasn't id or object")
|
||||
}
|
||||
val deleteId = value.id
|
||||
|
||||
val post = try {
|
||||
postQueryService.findByApId(deleteId)
|
||||
try {
|
||||
val actor = actorQueryService.findByUrl(deleteId)
|
||||
userService.deleteRemoteActor(actor.id)
|
||||
} catch (e: Exception) {
|
||||
logger.warn("FAILED delete id: {} is not found.", deleteId, e)
|
||||
}
|
||||
|
||||
try {
|
||||
val post = postQueryService.findByApId(deleteId)
|
||||
postService.deleteRemote(post)
|
||||
} catch (e: FailedToGetResourcesException) {
|
||||
logger.warn("FAILED delete id: {} is not found.", deleteId, e)
|
||||
return
|
||||
}
|
||||
|
||||
postRepository.delete(post.id)
|
||||
}
|
||||
|
||||
override fun isSupported(activityType: ActivityType): Boolean = activityType == ActivityType.Delete
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
package dev.usbharu.hideout.activitypub.service.activity.delete
|
||||
|
||||
import dev.usbharu.hideout.activitypub.service.common.APRequestService
|
||||
import dev.usbharu.hideout.application.external.Transaction
|
||||
import dev.usbharu.hideout.core.external.job.DeliverDeleteJob
|
||||
import dev.usbharu.hideout.core.external.job.DeliverDeleteJobParam
|
||||
import dev.usbharu.hideout.core.query.ActorQueryService
|
||||
import dev.usbharu.hideout.core.service.job.JobProcessor
|
||||
import org.springframework.stereotype.Service
|
||||
|
||||
@Service
|
||||
class APDeliverDeleteJobProcessor(
|
||||
private val apRequestService: APRequestService,
|
||||
private val actorQueryService: ActorQueryService,
|
||||
private val transaction: Transaction,
|
||||
private val deliverDeleteJob: DeliverDeleteJob
|
||||
) : JobProcessor<DeliverDeleteJobParam, DeliverDeleteJob> {
|
||||
override suspend fun process(param: DeliverDeleteJobParam): Unit = transaction.transaction {
|
||||
apRequestService.apPost(param.inbox, param.delete, actorQueryService.findById(param.signer))
|
||||
}
|
||||
|
||||
override fun job(): DeliverDeleteJob = deliverDeleteJob
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
package dev.usbharu.hideout.activitypub.service.activity.delete
|
||||
|
||||
import dev.usbharu.hideout.activitypub.domain.model.Delete
|
||||
import dev.usbharu.hideout.activitypub.domain.model.Tombstone
|
||||
import dev.usbharu.hideout.activitypub.domain.model.objects.ObjectValue
|
||||
import dev.usbharu.hideout.application.config.ApplicationConfig
|
||||
import dev.usbharu.hideout.core.domain.model.actor.Actor
|
||||
import dev.usbharu.hideout.core.domain.model.post.Post
|
||||
import dev.usbharu.hideout.core.external.job.DeliverDeleteJob
|
||||
import dev.usbharu.hideout.core.external.job.DeliverDeleteJobParam
|
||||
import dev.usbharu.hideout.core.query.ActorQueryService
|
||||
import dev.usbharu.hideout.core.query.FollowerQueryService
|
||||
import dev.usbharu.hideout.core.service.job.JobQueueParentService
|
||||
import org.springframework.stereotype.Service
|
||||
import java.time.Instant
|
||||
|
||||
interface APSendDeleteService {
|
||||
suspend fun sendDeleteNote(deletedPost: Post)
|
||||
suspend fun sendDeleteActor(deletedActor: Actor)
|
||||
}
|
||||
|
||||
@Service
|
||||
class APSendDeleteServiceImpl(
|
||||
private val jobQueueParentService: JobQueueParentService,
|
||||
private val delverDeleteJob: DeliverDeleteJob,
|
||||
private val followerQueryService: FollowerQueryService,
|
||||
private val actorQueryService: ActorQueryService,
|
||||
private val applicationConfig: ApplicationConfig
|
||||
) : APSendDeleteService {
|
||||
override suspend fun sendDeleteNote(deletedPost: Post) {
|
||||
val actor = actorQueryService.findById(deletedPost.actorId)
|
||||
val followersById = followerQueryService.findFollowersById(deletedPost.actorId)
|
||||
|
||||
val delete = Delete(
|
||||
actor = actor.url,
|
||||
id = "${applicationConfig.url}/delete/note/${deletedPost.id}",
|
||||
published = Instant.now().toString(),
|
||||
`object` = Tombstone(id = deletedPost.apId)
|
||||
)
|
||||
|
||||
followersById.forEach {
|
||||
val jobProps = DeliverDeleteJobParam(
|
||||
delete,
|
||||
it.inbox,
|
||||
actor.id
|
||||
)
|
||||
jobQueueParentService.scheduleTypeSafe(delverDeleteJob, jobProps)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun sendDeleteActor(deletedActor: Actor) {
|
||||
val followers = followerQueryService.findFollowersById(deletedActor.id)
|
||||
|
||||
val delete = Delete(
|
||||
actor = deletedActor.url,
|
||||
`object` = ObjectValue(emptyList(), `object` = deletedActor.url),
|
||||
id = "${applicationConfig.url}/delete/actor/${deletedActor.id}",
|
||||
published = Instant.now().toString()
|
||||
)
|
||||
|
||||
followers.forEach {
|
||||
DeliverDeleteJobParam(
|
||||
delete = delete,
|
||||
it.inbox,
|
||||
deletedActor.id
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -96,7 +96,7 @@ class APUserServiceImpl(
|
|||
name = person.preferredUsername
|
||||
?: throw IllegalActivityPubObjectException("preferredUsername is null"),
|
||||
domain = id.substringAfter("://").substringBefore("/"),
|
||||
screenName = person.name,
|
||||
screenName = person.name ?: person.preferredUsername,
|
||||
description = person.summary.orEmpty(),
|
||||
inbox = person.inbox,
|
||||
outbox = person.outbox,
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
package dev.usbharu.hideout.core.domain.model.deletedActor
|
||||
|
||||
import java.time.Instant
|
||||
|
||||
data class DeletedActor(
|
||||
val id: Long,
|
||||
val name: String,
|
||||
val domain: String,
|
||||
val publicKey: String,
|
||||
val deletedAt: Instant
|
||||
)
|
|
@ -0,0 +1,7 @@
|
|||
package dev.usbharu.hideout.core.domain.model.deletedActor
|
||||
|
||||
interface DeletedActorRepository {
|
||||
suspend fun save(deletedActor: DeletedActor): DeletedActor
|
||||
suspend fun delete(deletedActor: DeletedActor)
|
||||
suspend fun findById(id: Long): DeletedActor
|
||||
}
|
|
@ -2,6 +2,7 @@ package dev.usbharu.hideout.core.domain.model.post
|
|||
|
||||
import dev.usbharu.hideout.application.config.CharacterLimit
|
||||
import org.springframework.stereotype.Component
|
||||
import java.time.Instant
|
||||
|
||||
data class Post private constructor(
|
||||
val id: Long,
|
||||
|
@ -15,7 +16,8 @@ data class Post private constructor(
|
|||
val replyId: Long? = null,
|
||||
val sensitive: Boolean = false,
|
||||
val apId: String = url,
|
||||
val mediaIds: List<Long> = emptyList()
|
||||
val mediaIds: List<Long> = emptyList(),
|
||||
val delted: Boolean = false
|
||||
) {
|
||||
|
||||
@Component
|
||||
|
@ -71,8 +73,53 @@ data class Post private constructor(
|
|||
replyId = replyId,
|
||||
sensitive = sensitive,
|
||||
apId = apId,
|
||||
mediaIds = mediaIds
|
||||
mediaIds = mediaIds,
|
||||
delted = false
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
fun deleteOf(
|
||||
id: Long,
|
||||
visibility: Visibility,
|
||||
url: String,
|
||||
repostId: Long?,
|
||||
replyId: Long?,
|
||||
apId: String
|
||||
): Post {
|
||||
return Post(
|
||||
id = id,
|
||||
actorId = 0,
|
||||
overview = null,
|
||||
text = "",
|
||||
createdAt = Instant.EPOCH.toEpochMilli(),
|
||||
visibility = visibility,
|
||||
url = url,
|
||||
repostId = repostId,
|
||||
replyId = replyId,
|
||||
sensitive = false,
|
||||
apId = apId,
|
||||
mediaIds = emptyList(),
|
||||
delted = true
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun delete(): Post {
|
||||
return Post(
|
||||
id = this.id,
|
||||
actorId = 0,
|
||||
overview = null,
|
||||
text = "",
|
||||
createdAt = Instant.EPOCH.toEpochMilli(),
|
||||
visibility = visibility,
|
||||
url = url,
|
||||
repostId = repostId,
|
||||
replyId = replyId,
|
||||
sensitive = false,
|
||||
apId = apId,
|
||||
mediaIds = emptyList(),
|
||||
delted = true
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,4 +7,6 @@ interface ReactionRepository {
|
|||
suspend fun generateId(): Long
|
||||
suspend fun save(reaction: Reaction): Reaction
|
||||
suspend fun delete(reaction: Reaction): Reaction
|
||||
suspend fun deleteByPostId(postId: Long): Int
|
||||
suspend fun deleteByActorId(actorId: Long): Int
|
||||
}
|
||||
|
|
|
@ -28,4 +28,6 @@ interface RelationshipRepository {
|
|||
* @return 取得された[Relationship] 存在しない場合nullが返ります
|
||||
*/
|
||||
suspend fun findByUserIdAndTargetUserId(actorId: Long, targetActorId: Long): Relationship?
|
||||
|
||||
suspend fun deleteByActorIdOrTargetActorId(actorId: Long, targetActorId: Long)
|
||||
}
|
||||
|
|
|
@ -57,6 +57,12 @@ class RelationshipRepositoryImpl : RelationshipRepository {
|
|||
}.singleOrNull()
|
||||
?.toRelationships()
|
||||
}
|
||||
|
||||
override suspend fun deleteByActorIdOrTargetActorId(actorId: Long, targetActorId: Long) {
|
||||
Relationships.deleteWhere {
|
||||
Relationships.actorId.eq(actorId).or(Relationships.targetActorId.eq(targetActorId))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun ResultRow.toRelationships(): Relationship = Relationship(
|
||||
|
|
|
@ -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.Delete
|
||||
import kjob.core.dsl.ScheduleContext
|
||||
import kjob.core.job.JobProps
|
||||
import org.springframework.beans.factory.annotation.Qualifier
|
||||
import org.springframework.stereotype.Component
|
||||
|
||||
data class DeliverDeleteJobParam(
|
||||
val delete: Delete,
|
||||
val inbox: String,
|
||||
val signer: Long
|
||||
)
|
||||
|
||||
@Component
|
||||
class DeliverDeleteJob(@Qualifier("activitypub") private val objectMapper: ObjectMapper) :
|
||||
HideoutJob<DeliverDeleteJobParam, DeliverDeleteJob>("DeliverDeleteJob") {
|
||||
|
||||
val delete = string("delete")
|
||||
val inbox = string("inbox")
|
||||
val signer = long("signer")
|
||||
|
||||
override fun convert(value: DeliverDeleteJobParam): ScheduleContext<DeliverDeleteJob>.(DeliverDeleteJob) -> Unit = {
|
||||
props[delete] = objectMapper.writeValueAsString(value.delete)
|
||||
props[inbox] = value.inbox
|
||||
props[signer] = value.signer
|
||||
}
|
||||
|
||||
override fun convert(props: JobProps<DeliverDeleteJob>): DeliverDeleteJobParam = DeliverDeleteJobParam(
|
||||
objectMapper.readValue(props[delete]),
|
||||
props[inbox],
|
||||
props[signer]
|
||||
)
|
||||
}
|
|
@ -10,6 +10,17 @@ import org.springframework.stereotype.Component
|
|||
@Component
|
||||
class PostResultRowMapper(private val postBuilder: Post.PostBuilder) : ResultRowMapper<Post> {
|
||||
override fun map(resultRow: ResultRow): Post {
|
||||
if (resultRow[Posts.deleted]) {
|
||||
return postBuilder.deleteOf(
|
||||
id = resultRow[Posts.id],
|
||||
visibility = Visibility.values().first { it.ordinal == resultRow[Posts.visibility] },
|
||||
url = resultRow[Posts.url],
|
||||
repostId = resultRow[Posts.repostId],
|
||||
replyId = resultRow[Posts.replyId],
|
||||
apId = resultRow[Posts.apId]
|
||||
)
|
||||
}
|
||||
|
||||
return postBuilder.of(
|
||||
id = resultRow[Posts.id],
|
||||
actorId = resultRow[Posts.actorId],
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
package dev.usbharu.hideout.core.infrastructure.exposedquery
|
||||
|
||||
import dev.usbharu.hideout.core.domain.exception.FailedToGetResourcesException
|
||||
import dev.usbharu.hideout.core.domain.model.deletedActor.DeletedActor
|
||||
import dev.usbharu.hideout.core.infrastructure.exposedrepository.DeletedActors
|
||||
import dev.usbharu.hideout.core.infrastructure.exposedrepository.toDeletedActor
|
||||
import dev.usbharu.hideout.core.query.DeletedActorQueryService
|
||||
import dev.usbharu.hideout.util.singleOr
|
||||
import org.jetbrains.exposed.sql.and
|
||||
import org.jetbrains.exposed.sql.select
|
||||
import org.springframework.stereotype.Repository
|
||||
|
||||
@Repository
|
||||
class DeletedActorQueryServiceImpl : DeletedActorQueryService {
|
||||
override suspend fun findByNameAndDomain(name: String, domain: String): DeletedActor {
|
||||
return DeletedActors
|
||||
.select { DeletedActors.name eq name and (DeletedActors.domain eq domain) }
|
||||
.singleOr {
|
||||
FailedToGetResourcesException("name: $name domain: $domain was not exist or duplicate.", it)
|
||||
}
|
||||
.toDeletedActor()
|
||||
}
|
||||
}
|
|
@ -33,4 +33,7 @@ class PostQueryServiceImpl(
|
|||
.select { Posts.apId eq string }
|
||||
.let(postQueryMapper::map)
|
||||
.singleOr { FailedToGetResourcesException("apId: $string is duplicate or does not exist.", it) }
|
||||
|
||||
override suspend fun findByActorId(actorId: Long): List<Post> =
|
||||
Posts.leftJoin(PostsMedia).select { Posts.actorId eq actorId }.let(postQueryMapper::map)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,71 @@
|
|||
package dev.usbharu.hideout.core.infrastructure.exposedrepository
|
||||
|
||||
import dev.usbharu.hideout.core.domain.exception.FailedToGetResourcesException
|
||||
import dev.usbharu.hideout.core.domain.model.deletedActor.DeletedActor
|
||||
import dev.usbharu.hideout.core.domain.model.deletedActor.DeletedActorRepository
|
||||
import dev.usbharu.hideout.util.singleOr
|
||||
import org.jetbrains.exposed.sql.*
|
||||
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
|
||||
import org.jetbrains.exposed.sql.javatime.timestamp
|
||||
import org.springframework.stereotype.Repository
|
||||
|
||||
@Repository
|
||||
class DeletedActorRepositoryImpl : DeletedActorRepository {
|
||||
override suspend fun save(deletedActor: DeletedActor): DeletedActor {
|
||||
val singleOrNull = DeletedActors.select { DeletedActors.id eq deletedActor.id }.singleOrNull()
|
||||
|
||||
if (singleOrNull == null) {
|
||||
DeletedActors.insert {
|
||||
it[DeletedActors.id] = deletedActor.id
|
||||
it[DeletedActors.name] = deletedActor.name
|
||||
it[DeletedActors.domain] = deletedActor.domain
|
||||
it[DeletedActors.publicKey] = deletedActor.publicKey
|
||||
it[DeletedActors.deletedAt] = deletedActor.deletedAt
|
||||
}
|
||||
} else {
|
||||
DeletedActors.update({ DeletedActors.id eq deletedActor.id }) {
|
||||
it[DeletedActors.name] = deletedActor.name
|
||||
it[DeletedActors.domain] = deletedActor.domain
|
||||
it[DeletedActors.publicKey] = deletedActor.publicKey
|
||||
it[DeletedActors.deletedAt] = deletedActor.deletedAt
|
||||
}
|
||||
}
|
||||
return deletedActor
|
||||
}
|
||||
|
||||
override suspend fun delete(deletedActor: DeletedActor) {
|
||||
DeletedActors.deleteWhere { DeletedActors.id eq deletedActor.id }
|
||||
}
|
||||
|
||||
override suspend fun findById(id: Long): DeletedActor {
|
||||
val singleOr = DeletedActors.select { DeletedActors.id eq id }
|
||||
.singleOr { FailedToGetResourcesException("id: $id was not exist or duplicate", it) }
|
||||
|
||||
return deletedActor(singleOr)
|
||||
}
|
||||
}
|
||||
|
||||
fun ResultRow.toDeletedActor(): DeletedActor = deletedActor(this)
|
||||
|
||||
private fun deletedActor(singleOr: ResultRow): DeletedActor {
|
||||
return DeletedActor(
|
||||
singleOr[DeletedActors.id],
|
||||
singleOr[DeletedActors.name],
|
||||
singleOr[DeletedActors.domain],
|
||||
singleOr[DeletedActors.publicKey],
|
||||
singleOr[DeletedActors.deletedAt]
|
||||
)
|
||||
}
|
||||
|
||||
object DeletedActors : Table("deleted_actors") {
|
||||
val id = long("id")
|
||||
val name = varchar("name", 300)
|
||||
val domain = varchar("domain", 255)
|
||||
val publicKey = varchar("public_key", 10000).uniqueIndex()
|
||||
val deletedAt = timestamp("deleted_at")
|
||||
override val primaryKey: PrimaryKey = PrimaryKey(id)
|
||||
|
||||
init {
|
||||
uniqueIndex(name, domain)
|
||||
}
|
||||
}
|
|
@ -33,6 +33,7 @@ class PostRepositoryImpl(
|
|||
it[replyId] = post.replyId
|
||||
it[sensitive] = post.sensitive
|
||||
it[apId] = post.apId
|
||||
it[deleted] = post.delted
|
||||
}
|
||||
PostsMedia.batchInsert(post.mediaIds) {
|
||||
this[PostsMedia.postId] = post.id
|
||||
|
@ -57,6 +58,7 @@ class PostRepositoryImpl(
|
|||
it[replyId] = post.replyId
|
||||
it[sensitive] = post.sensitive
|
||||
it[apId] = post.apId
|
||||
it[deleted] = post.delted
|
||||
}
|
||||
}
|
||||
return singleOrNull == null
|
||||
|
@ -85,6 +87,7 @@ object Posts : Table() {
|
|||
val replyId: Column<Long?> = long("reply_id").references(id).nullable()
|
||||
val sensitive: Column<Boolean> = bool("sensitive").default(false)
|
||||
val apId: Column<String> = varchar("ap_id", 100).uniqueIndex()
|
||||
val deleted = bool("deleted").default(false)
|
||||
override val primaryKey: PrimaryKey = PrimaryKey(id)
|
||||
}
|
||||
|
||||
|
|
|
@ -42,6 +42,18 @@ class ReactionRepositoryImpl(
|
|||
}
|
||||
return reaction
|
||||
}
|
||||
|
||||
override suspend fun deleteByPostId(postId: Long): Int {
|
||||
return Reactions.deleteWhere {
|
||||
Reactions.postId eq postId
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun deleteByActorId(actorId: Long): Int {
|
||||
return Reactions.deleteWhere {
|
||||
Reactions.actorId eq actorId
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun ResultRow.toReaction(): Reaction {
|
||||
|
@ -55,8 +67,10 @@ fun ResultRow.toReaction(): Reaction {
|
|||
|
||||
object Reactions : LongIdTable("reactions") {
|
||||
val emojiId: Column<Long> = long("emoji_id")
|
||||
val postId: Column<Long> = long("post_id").references(Posts.id)
|
||||
val actorId: Column<Long> = long("actor_id").references(Actors.id)
|
||||
val postId: Column<Long> = long("post_id")
|
||||
.references(Posts.id, onDelete = ReferenceOption.CASCADE, onUpdate = ReferenceOption.CASCADE)
|
||||
val actorId: Column<Long> = long("actor_id")
|
||||
.references(Actors.id, onDelete = ReferenceOption.CASCADE, onUpdate = ReferenceOption.CASCADE)
|
||||
|
||||
init {
|
||||
uniqueIndex(emojiId, postId, actorId)
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
package dev.usbharu.hideout.core.query
|
||||
|
||||
import dev.usbharu.hideout.core.domain.model.deletedActor.DeletedActor
|
||||
|
||||
interface DeletedActorQueryService {
|
||||
suspend fun findByNameAndDomain(name: String, domain: String): DeletedActor
|
||||
}
|
|
@ -8,4 +8,5 @@ interface PostQueryService {
|
|||
suspend fun findById(id: Long): Post
|
||||
suspend fun findByUrl(url: String): Post
|
||||
suspend fun findByApId(string: String): Post
|
||||
suspend fun findByActorId(actorId: Long): List<Post>
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ interface RelationshipQueryService {
|
|||
|
||||
suspend fun findByTargetIdAndFollowing(targetId: Long, following: Boolean): List<Relationship>
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
@Suppress("LongParameterList", "FunctionMaxLength")
|
||||
suspend fun findByTargetIdAndFollowRequestAndIgnoreFollowRequest(
|
||||
maxId: Long?,
|
||||
sinceId: Long?,
|
||||
|
|
|
@ -7,4 +7,7 @@ import org.springframework.stereotype.Service
|
|||
interface PostService {
|
||||
suspend fun createLocal(post: PostCreateDto): Post
|
||||
suspend fun createRemote(post: Post): Post
|
||||
suspend fun deleteLocal(post: Post)
|
||||
suspend fun deleteRemote(post: Post)
|
||||
suspend fun deleteByActor(actorId: Long)
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import dev.usbharu.hideout.core.domain.exception.UserNotFoundException
|
|||
import dev.usbharu.hideout.core.domain.model.actor.ActorRepository
|
||||
import dev.usbharu.hideout.core.domain.model.post.Post
|
||||
import dev.usbharu.hideout.core.domain.model.post.PostRepository
|
||||
import dev.usbharu.hideout.core.domain.model.reaction.ReactionRepository
|
||||
import dev.usbharu.hideout.core.query.PostQueryService
|
||||
import dev.usbharu.hideout.core.service.timeline.TimelineService
|
||||
import org.jetbrains.exposed.exceptions.ExposedSQLException
|
||||
|
@ -20,7 +21,8 @@ class PostServiceImpl(
|
|||
private val timelineService: TimelineService,
|
||||
private val postQueryService: PostQueryService,
|
||||
private val postBuilder: Post.PostBuilder,
|
||||
private val apSendCreateService: ApSendCreateService
|
||||
private val apSendCreateService: ApSendCreateService,
|
||||
private val reactionRepository: ReactionRepository
|
||||
) : PostService {
|
||||
|
||||
override suspend fun createLocal(post: PostCreateDto): Post {
|
||||
|
@ -38,6 +40,26 @@ class PostServiceImpl(
|
|||
return createdPost
|
||||
}
|
||||
|
||||
override suspend fun deleteLocal(post: Post) {
|
||||
if (post.delted) {
|
||||
return
|
||||
}
|
||||
reactionRepository.deleteByPostId(post.id)
|
||||
postRepository.save(post.delete())
|
||||
}
|
||||
|
||||
override suspend fun deleteRemote(post: Post) {
|
||||
if (post.delted) {
|
||||
return
|
||||
}
|
||||
reactionRepository.deleteByPostId(post.id)
|
||||
postRepository.save(post.delete())
|
||||
}
|
||||
|
||||
override suspend fun deleteByActor(actorId: Long) {
|
||||
postQueryService.findByActorId(actorId).filterNot { it.delted }.forEach { postRepository.save(it.delete()) }
|
||||
}
|
||||
|
||||
private suspend fun internalCreate(post: Post, isLocal: Boolean): Post {
|
||||
return try {
|
||||
if (postRepository.save(post)) {
|
||||
|
|
|
@ -13,4 +13,8 @@ interface UserService {
|
|||
suspend fun createRemoteUser(user: RemoteUserCreateDto): Actor
|
||||
|
||||
suspend fun updateUser(userId: Long, updateUserDto: UpdateUserDto)
|
||||
|
||||
suspend fun deleteRemoteActor(actorId: Long)
|
||||
|
||||
suspend fun deleteLocalUser(userId: Long)
|
||||
}
|
||||
|
|
|
@ -1,12 +1,20 @@
|
|||
package dev.usbharu.hideout.core.service.user
|
||||
|
||||
import dev.usbharu.hideout.activitypub.service.activity.delete.APSendDeleteService
|
||||
import dev.usbharu.hideout.application.config.ApplicationConfig
|
||||
import dev.usbharu.hideout.core.domain.exception.FailedToGetResourcesException
|
||||
import dev.usbharu.hideout.core.domain.model.actor.Actor
|
||||
import dev.usbharu.hideout.core.domain.model.actor.ActorRepository
|
||||
import dev.usbharu.hideout.core.domain.model.deletedActor.DeletedActor
|
||||
import dev.usbharu.hideout.core.domain.model.deletedActor.DeletedActorRepository
|
||||
import dev.usbharu.hideout.core.domain.model.reaction.ReactionRepository
|
||||
import dev.usbharu.hideout.core.domain.model.relationship.RelationshipRepository
|
||||
import dev.usbharu.hideout.core.domain.model.userdetails.UserDetail
|
||||
import dev.usbharu.hideout.core.domain.model.userdetails.UserDetailRepository
|
||||
import dev.usbharu.hideout.core.query.ActorQueryService
|
||||
import dev.usbharu.hideout.core.query.DeletedActorQueryService
|
||||
import dev.usbharu.hideout.core.service.instance.InstanceService
|
||||
import dev.usbharu.hideout.core.service.post.PostService
|
||||
import org.jetbrains.exposed.exceptions.ExposedSQLException
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.stereotype.Service
|
||||
|
@ -14,6 +22,7 @@ import org.springframework.transaction.annotation.Transactional
|
|||
import java.time.Instant
|
||||
|
||||
@Service
|
||||
@Suppress("LongParameterList")
|
||||
class UserServiceImpl(
|
||||
private val actorRepository: ActorRepository,
|
||||
private val userAuthService: UserAuthService,
|
||||
|
@ -21,7 +30,14 @@ class UserServiceImpl(
|
|||
private val actorBuilder: Actor.UserBuilder,
|
||||
private val applicationConfig: ApplicationConfig,
|
||||
private val instanceService: InstanceService,
|
||||
private val userDetailRepository: UserDetailRepository
|
||||
private val userDetailRepository: UserDetailRepository,
|
||||
private val deletedActorRepository: DeletedActorRepository,
|
||||
private val deletedActorQueryService: DeletedActorQueryService,
|
||||
private val reactionRepository: ReactionRepository,
|
||||
private val relationshipRepository: RelationshipRepository,
|
||||
private val postService: PostService,
|
||||
private val apSendDeleteService: APSendDeleteService
|
||||
|
||||
) :
|
||||
UserService {
|
||||
|
||||
|
@ -60,6 +76,14 @@ class UserServiceImpl(
|
|||
@Transactional
|
||||
override suspend fun createRemoteUser(user: RemoteUserCreateDto): Actor {
|
||||
logger.info("START Create New remote user. name: {} url: {}", user.name, user.url)
|
||||
|
||||
try {
|
||||
deletedActorQueryService.findByNameAndDomain(user.name, user.domain)
|
||||
logger.warn("FAILED Deleted actor. user: ${user.name} domain: ${user.domain}")
|
||||
throw IllegalStateException("Cannot create Deleted actor.")
|
||||
} catch (_: FailedToGetResourcesException) {
|
||||
}
|
||||
|
||||
@Suppress("TooGenericExceptionCaught")
|
||||
val instance = try {
|
||||
instanceService.fetchInstance(user.url, user.sharedInbox)
|
||||
|
@ -117,6 +141,47 @@ class UserServiceImpl(
|
|||
)
|
||||
}
|
||||
|
||||
override suspend fun deleteRemoteActor(actorId: Long) {
|
||||
val actor = actorQueryService.findById(actorId)
|
||||
val deletedActor = DeletedActor(
|
||||
actor.id,
|
||||
actor.name,
|
||||
actor.domain,
|
||||
actor.publicKey,
|
||||
Instant.now()
|
||||
)
|
||||
relationshipRepository.deleteByActorIdOrTargetActorId(actorId, actorId)
|
||||
|
||||
reactionRepository.deleteByActorId(actorId)
|
||||
|
||||
postService.deleteByActor(actorId)
|
||||
|
||||
actorRepository.delete(actor.id)
|
||||
deletedActorRepository.save(deletedActor)
|
||||
}
|
||||
|
||||
override suspend fun deleteLocalUser(userId: Long) {
|
||||
val actor = actorQueryService.findById(userId)
|
||||
apSendDeleteService.sendDeleteActor(actor)
|
||||
val deletedActor = DeletedActor(
|
||||
actor.id,
|
||||
actor.name,
|
||||
actor.domain,
|
||||
actor.publicKey,
|
||||
Instant.now()
|
||||
)
|
||||
relationshipRepository.deleteByActorIdOrTargetActorId(userId, userId)
|
||||
|
||||
reactionRepository.deleteByActorId(actor.id)
|
||||
|
||||
postService.deleteByActor(actor.id)
|
||||
actorRepository.delete(actor.id)
|
||||
val userDetail =
|
||||
userDetailRepository.findByActorId(actor.id) ?: throw IllegalStateException("user detail not found.")
|
||||
userDetailRepository.delete(userDetail)
|
||||
deletedActorRepository.save(deletedActor)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val logger = LoggerFactory.getLogger(UserServiceImpl::class.java)
|
||||
}
|
||||
|
|
|
@ -67,7 +67,7 @@ create table if not exists meta_info
|
|||
create table if not exists posts
|
||||
(
|
||||
id bigint primary key,
|
||||
actor_id bigint not null,
|
||||
actor_id bigint not null,
|
||||
overview varchar(100) null,
|
||||
text varchar(3000) not null,
|
||||
created_at bigint not null,
|
||||
|
@ -76,7 +76,8 @@ create table if not exists posts
|
|||
repost_id bigint null,
|
||||
reply_id bigint null,
|
||||
"sensitive" boolean default false not null,
|
||||
ap_id varchar(100) not null unique
|
||||
ap_id varchar(100) not null unique,
|
||||
deleted boolean default false not null
|
||||
);
|
||||
alter table posts
|
||||
add constraint fk_posts_actor_id__id foreign key (actor_id) references actors (id) on delete restrict on update restrict;
|
||||
|
@ -196,4 +197,18 @@ create table if not exists relationships
|
|||
constraint fk_relationships_actor_id__id foreign key (actor_id) references actors (id) on delete restrict on update restrict,
|
||||
constraint fk_relationships_target_actor_id__id foreign key (target_actor_id) references actors (id) on delete restrict on update restrict,
|
||||
unique (actor_id, target_actor_id)
|
||||
);
|
||||
|
||||
insert into actors (id, name, domain, screen_name, description, inbox, outbox, url, public_key, private_key, created_at,
|
||||
key_id, following, followers, instance, locked)
|
||||
values (0, 'ghost', '', '', '', '', '', '', '', null, 0, '', '', '', null, true);
|
||||
|
||||
create table if not exists deleted_actors
|
||||
(
|
||||
id bigint primary key,
|
||||
"name" varchar(300) not null,
|
||||
domain varchar(255) not null,
|
||||
public_key varchar(10000) not null,
|
||||
deleted_at timestamp not null,
|
||||
unique ("name", domain)
|
||||
)
|
||||
|
|
|
@ -5,6 +5,7 @@ import dev.usbharu.hideout.application.config.CharacterLimit
|
|||
import dev.usbharu.hideout.core.domain.model.actor.ActorRepository
|
||||
import dev.usbharu.hideout.core.domain.model.post.Post
|
||||
import dev.usbharu.hideout.core.domain.model.post.PostRepository
|
||||
import dev.usbharu.hideout.core.domain.model.reaction.ReactionRepository
|
||||
import dev.usbharu.hideout.core.query.PostQueryService
|
||||
import dev.usbharu.hideout.core.service.timeline.TimelineService
|
||||
import kotlinx.coroutines.test.runTest
|
||||
|
@ -45,6 +46,9 @@ class PostServiceImplTest {
|
|||
@Mock
|
||||
private lateinit var apSendCreateService: ApSendCreateService
|
||||
|
||||
@Mock
|
||||
private lateinit var reactionRepository: ReactionRepository
|
||||
|
||||
@InjectMocks
|
||||
private lateinit var postServiceImpl: PostServiceImpl
|
||||
|
||||
|
|
|
@ -4,9 +4,11 @@ package dev.usbharu.hideout.core.service.user
|
|||
|
||||
import dev.usbharu.hideout.application.config.ApplicationConfig
|
||||
import dev.usbharu.hideout.application.config.CharacterLimit
|
||||
import dev.usbharu.hideout.core.domain.exception.FailedToGetResourcesException
|
||||
import dev.usbharu.hideout.core.domain.model.actor.Actor
|
||||
import dev.usbharu.hideout.core.domain.model.actor.ActorRepository
|
||||
import dev.usbharu.hideout.core.domain.model.post.Post
|
||||
import dev.usbharu.hideout.core.query.DeletedActorQueryService
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.Test
|
||||
|
@ -34,13 +36,19 @@ class ActorServiceTest {
|
|||
}
|
||||
val userService =
|
||||
UserServiceImpl(
|
||||
actorRepository,
|
||||
userAuthService,
|
||||
mock(),
|
||||
actorBuilder,
|
||||
testApplicationConfig,
|
||||
mock(),
|
||||
mock()
|
||||
actorRepository = actorRepository,
|
||||
userAuthService = userAuthService,
|
||||
actorQueryService = mock(),
|
||||
actorBuilder = actorBuilder,
|
||||
applicationConfig = testApplicationConfig,
|
||||
instanceService = mock(),
|
||||
userDetailRepository = mock(),
|
||||
deletedActorRepository = mock(),
|
||||
deletedActorQueryService = mock(),
|
||||
reactionRepository = mock(),
|
||||
relationshipRepository = mock(),
|
||||
postService = mock(),
|
||||
apSendDeleteService = mock()
|
||||
)
|
||||
userService.createLocalUser(UserCreateDto("test", "testUser", "XXXXXXXXXXXXX", "test"))
|
||||
verify(actorRepository, times(1)).save(any())
|
||||
|
@ -65,8 +73,30 @@ class ActorServiceTest {
|
|||
val actorRepository = mock<ActorRepository> {
|
||||
onBlocking { nextId() } doReturn 113345L
|
||||
}
|
||||
val deletedActorQueryService = mock<DeletedActorQueryService> {
|
||||
onBlocking {
|
||||
findByNameAndDomain(
|
||||
eq("test"),
|
||||
eq("remote.example.com")
|
||||
)
|
||||
} doAnswer { throw FailedToGetResourcesException() }
|
||||
}
|
||||
val userService =
|
||||
UserServiceImpl(actorRepository, mock(), mock(), actorBuilder, testApplicationConfig, mock(), mock())
|
||||
UserServiceImpl(
|
||||
actorRepository = actorRepository,
|
||||
userAuthService = mock(),
|
||||
actorQueryService = mock(),
|
||||
actorBuilder = actorBuilder,
|
||||
applicationConfig = testApplicationConfig,
|
||||
instanceService = mock(),
|
||||
userDetailRepository = mock(),
|
||||
deletedActorRepository = mock(),
|
||||
deletedActorQueryService = deletedActorQueryService,
|
||||
reactionRepository = mock(),
|
||||
relationshipRepository = mock(),
|
||||
postService = mock(),
|
||||
apSendDeleteService = mock()
|
||||
)
|
||||
val user = RemoteUserCreateDto(
|
||||
name = "test",
|
||||
domain = "remote.example.com",
|
||||
|
|
Loading…
Reference in New Issue