mirror of https://github.com/usbharu/Hideout.git
feat: 削除された投稿はレコードを完全に削除せず、返信などの情報を維持したまま削除されるように
This commit is contained in:
parent
8d1f339a5d
commit
8a18780963
|
@ -8,15 +8,15 @@ 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.domain.exception.FailedToGetResourcesException
|
import dev.usbharu.hideout.core.domain.exception.FailedToGetResourcesException
|
||||||
import dev.usbharu.hideout.core.domain.model.post.PostRepository
|
|
||||||
import dev.usbharu.hideout.core.query.PostQueryService
|
import dev.usbharu.hideout.core.query.PostQueryService
|
||||||
|
import dev.usbharu.hideout.core.service.post.PostService
|
||||||
import org.springframework.stereotype.Service
|
import org.springframework.stereotype.Service
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
class APDeleteProcessor(
|
class APDeleteProcessor(
|
||||||
transaction: Transaction,
|
transaction: Transaction,
|
||||||
private val postQueryService: PostQueryService,
|
private val postQueryService: PostQueryService,
|
||||||
private val postRepository: PostRepository
|
private val postService: PostService
|
||||||
) :
|
) :
|
||||||
AbstractActivityPubProcessor<Delete>(transaction) {
|
AbstractActivityPubProcessor<Delete>(transaction) {
|
||||||
override suspend fun internalProcess(activity: ActivityPubProcessContext<Delete>) {
|
override suspend fun internalProcess(activity: ActivityPubProcessContext<Delete>) {
|
||||||
|
@ -33,7 +33,7 @@ class APDeleteProcessor(
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
postRepository.delete(post.id)
|
postService.deleteRemote(post)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun isSupported(activityType: ActivityType): Boolean = activityType == ActivityType.Delete
|
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,50 @@
|
||||||
|
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.application.config.ApplicationConfig
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
@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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -2,6 +2,7 @@ package dev.usbharu.hideout.core.domain.model.post
|
||||||
|
|
||||||
import dev.usbharu.hideout.application.config.CharacterLimit
|
import dev.usbharu.hideout.application.config.CharacterLimit
|
||||||
import org.springframework.stereotype.Component
|
import org.springframework.stereotype.Component
|
||||||
|
import java.time.Instant
|
||||||
|
|
||||||
data class Post private constructor(
|
data class Post private constructor(
|
||||||
val id: Long,
|
val id: Long,
|
||||||
|
@ -15,7 +16,8 @@ data class Post private constructor(
|
||||||
val replyId: Long? = null,
|
val replyId: Long? = null,
|
||||||
val sensitive: Boolean = false,
|
val sensitive: Boolean = false,
|
||||||
val apId: String = url,
|
val apId: String = url,
|
||||||
val mediaIds: List<Long> = emptyList()
|
val mediaIds: List<Long> = emptyList(),
|
||||||
|
val delted: Boolean = false
|
||||||
) {
|
) {
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
|
@ -71,8 +73,52 @@ data class Post private constructor(
|
||||||
replyId = replyId,
|
replyId = replyId,
|
||||||
sensitive = sensitive,
|
sensitive = sensitive,
|
||||||
apId = apId,
|
apId = apId,
|
||||||
mediaIds = mediaIds
|
mediaIds = mediaIds,
|
||||||
|
delted = false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
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 generateId(): Long
|
||||||
suspend fun save(reaction: Reaction): Reaction
|
suspend fun save(reaction: Reaction): Reaction
|
||||||
suspend fun delete(reaction: Reaction): Reaction
|
suspend fun delete(reaction: Reaction): Reaction
|
||||||
|
suspend fun deleteByPostId(postId: Long): Int
|
||||||
|
suspend fun deleteByActorId(actorId: Long): Int
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
@Component
|
||||||
class PostResultRowMapper(private val postBuilder: Post.PostBuilder) : ResultRowMapper<Post> {
|
class PostResultRowMapper(private val postBuilder: Post.PostBuilder) : ResultRowMapper<Post> {
|
||||||
override fun map(resultRow: ResultRow): Post {
|
override fun map(resultRow: ResultRow): Post {
|
||||||
|
if (resultRow[Posts.deleted]) {
|
||||||
|
return postBuilder.deleteOf(
|
||||||
|
resultRow[Posts.id],
|
||||||
|
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(
|
return postBuilder.of(
|
||||||
id = resultRow[Posts.id],
|
id = resultRow[Posts.id],
|
||||||
actorId = resultRow[Posts.actorId],
|
actorId = resultRow[Posts.actorId],
|
||||||
|
|
|
@ -85,6 +85,7 @@ object Posts : Table() {
|
||||||
val replyId: Column<Long?> = long("reply_id").references(id).nullable()
|
val replyId: Column<Long?> = long("reply_id").references(id).nullable()
|
||||||
val sensitive: Column<Boolean> = bool("sensitive").default(false)
|
val sensitive: Column<Boolean> = bool("sensitive").default(false)
|
||||||
val apId: Column<String> = varchar("ap_id", 100).uniqueIndex()
|
val apId: Column<String> = varchar("ap_id", 100).uniqueIndex()
|
||||||
|
val deleted = bool("deleted").default(false)
|
||||||
override val primaryKey: PrimaryKey = PrimaryKey(id)
|
override val primaryKey: PrimaryKey = PrimaryKey(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -42,6 +42,18 @@ class ReactionRepositoryImpl(
|
||||||
}
|
}
|
||||||
return reaction
|
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 {
|
fun ResultRow.toReaction(): Reaction {
|
||||||
|
@ -55,8 +67,10 @@ fun ResultRow.toReaction(): Reaction {
|
||||||
|
|
||||||
object Reactions : LongIdTable("reactions") {
|
object Reactions : LongIdTable("reactions") {
|
||||||
val emojiId: Column<Long> = long("emoji_id")
|
val emojiId: Column<Long> = long("emoji_id")
|
||||||
val postId: Column<Long> = long("post_id").references(Posts.id)
|
val postId: Column<Long> = long("post_id")
|
||||||
val actorId: Column<Long> = long("actor_id").references(Actors.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 {
|
init {
|
||||||
uniqueIndex(emojiId, postId, actorId)
|
uniqueIndex(emojiId, postId, actorId)
|
||||||
|
|
|
@ -7,4 +7,6 @@ import org.springframework.stereotype.Service
|
||||||
interface PostService {
|
interface PostService {
|
||||||
suspend fun createLocal(post: PostCreateDto): Post
|
suspend fun createLocal(post: PostCreateDto): Post
|
||||||
suspend fun createRemote(post: Post): Post
|
suspend fun createRemote(post: Post): Post
|
||||||
|
suspend fun deleteLocal(post: Post)
|
||||||
|
suspend fun deleteRemote(post: Post)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.actor.ActorRepository
|
||||||
import dev.usbharu.hideout.core.domain.model.post.Post
|
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.post.PostRepository
|
||||||
|
import dev.usbharu.hideout.core.domain.model.reaction.ReactionRepository
|
||||||
import dev.usbharu.hideout.core.query.PostQueryService
|
import dev.usbharu.hideout.core.query.PostQueryService
|
||||||
import dev.usbharu.hideout.core.service.timeline.TimelineService
|
import dev.usbharu.hideout.core.service.timeline.TimelineService
|
||||||
import org.jetbrains.exposed.exceptions.ExposedSQLException
|
import org.jetbrains.exposed.exceptions.ExposedSQLException
|
||||||
|
@ -20,7 +21,8 @@ class PostServiceImpl(
|
||||||
private val timelineService: TimelineService,
|
private val timelineService: TimelineService,
|
||||||
private val postQueryService: PostQueryService,
|
private val postQueryService: PostQueryService,
|
||||||
private val postBuilder: Post.PostBuilder,
|
private val postBuilder: Post.PostBuilder,
|
||||||
private val apSendCreateService: ApSendCreateService
|
private val apSendCreateService: ApSendCreateService,
|
||||||
|
private val reactionRepository: ReactionRepository
|
||||||
) : PostService {
|
) : PostService {
|
||||||
|
|
||||||
override suspend fun createLocal(post: PostCreateDto): Post {
|
override suspend fun createLocal(post: PostCreateDto): Post {
|
||||||
|
@ -38,6 +40,16 @@ class PostServiceImpl(
|
||||||
return createdPost
|
return createdPost
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun deleteLocal(post: Post) {
|
||||||
|
reactionRepository.deleteByPostId(post.id)
|
||||||
|
postRepository.save(post.delete())
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun deleteRemote(post: Post) {
|
||||||
|
reactionRepository.deleteByPostId(post.id)
|
||||||
|
postRepository.save(post.delete())
|
||||||
|
}
|
||||||
|
|
||||||
private suspend fun internalCreate(post: Post, isLocal: Boolean): Post {
|
private suspend fun internalCreate(post: Post, isLocal: Boolean): Post {
|
||||||
return try {
|
return try {
|
||||||
if (postRepository.save(post)) {
|
if (postRepository.save(post)) {
|
||||||
|
|
|
@ -76,7 +76,8 @@ create table if not exists posts
|
||||||
repost_id bigint null,
|
repost_id bigint null,
|
||||||
reply_id bigint null,
|
reply_id bigint null,
|
||||||
"sensitive" boolean default false not 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
|
alter table posts
|
||||||
add constraint fk_posts_actor_id__id foreign key (actor_id) references actors (id) on delete restrict on update restrict;
|
add constraint fk_posts_actor_id__id foreign key (actor_id) references actors (id) on delete restrict on update restrict;
|
||||||
|
@ -196,4 +197,8 @@ 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_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,
|
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)
|
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);
|
||||||
|
|
Loading…
Reference in New Issue