Put Federation on async executor

This commit is contained in:
Tim Zöller 2026-04-07 19:40:04 +02:00
parent fde80672f2
commit 0e32aab244
2 changed files with 31 additions and 5 deletions

View file

@ -201,13 +201,27 @@ public class CommentController {
return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
}
// Soft delete
// Soft delete locally
comment.setDeleted(true);
commentRepository.save(comment);
log.info("User {} deleted comment {}", user.getUsername(), commentId);
// TODO: Send ActivityPub Delete activity to followers if activity is public
// Federate the deletion to remote followers so they tombstone their cached
// copy of the Note. The visibility check mirrors createComment(): the
// original Create was only sent if the parent activity was PUBLIC or
// FOLLOWERS, so the Delete needs to follow the same audience rule. The
// commentUri must match exactly what was used in the original Create
// activity, otherwise remote servers won't be able to match the tombstone
// to the cached note.
Activity activity = activityRepository.findById(activityId).orElse(null);
if (activity != null
&& (activity.getVisibility() == Activity.Visibility.PUBLIC
|| activity.getVisibility() == Activity.Visibility.FOLLOWERS)) {
String commentUri = baseUrl + "/activities/" + activityId + "/comments/" + commentId;
federationService.sendDeleteActivity(commentUri, user);
log.info("Sent Delete federation for comment {} on activity {}", commentId, activityId);
}
return ResponseEntity.noContent().build();
}

View file

@ -311,10 +311,16 @@ public class FederationService {
}
/**
* Get all follower inbox URLs for a local user.
* Get the inbox URLs of all <em>remote</em> followers of a local user.
*
* <p>Local followers are deliberately skipped: they live on the same server and
* see new activities via the local timeline queries, so there is nothing to
* federate to them. Without this filter, every call would attempt to
* {@code fetchRemoteActor(null)} for each local follower row, log a stack trace
* at ERROR level, and then drop the resulting null from the inbox list.
*
* @param userId the local user's ID
* @return list of inbox URLs
* @return list of remote follower inbox URLs (deduplicated, shared inbox preferred)
*/
@Transactional(readOnly = true)
public List<String> getFollowerInboxes(UUID userId) {
@ -325,6 +331,7 @@ public class FederationService {
List<Follow> followers = followRepository.findAcceptedFollowersByActorUri(actorUri);
return followers.stream()
.filter(follow -> follow.getRemoteActorUri() != null) // skip local followers (no federation needed)
.map(follow -> {
try {
RemoteActor actor = remoteActorRepository.findByActorUri(follow.getRemoteActorUri())
@ -529,9 +536,14 @@ public class FederationService {
/**
* Send a Delete activity to notify followers that an object has been deleted.
*
* @param objectUri the URI of the deleted object (e.g., activity URI)
* <p>Runs on the {@code taskExecutor} pool. Used for both activity deletes and
* comment (Note) deletes the user-facing HTTP response shouldn't wait on the
* federation fanout to remote follower inboxes.
*
* @param objectUri the URI of the deleted object (e.g., activity URI or comment Note URI)
* @param sender the user who deleted the object
*/
@Async("taskExecutor")
public void sendDeleteActivity(String objectUri, User sender) {
try {
String deleteId = baseUrl + "/activities/delete/" + UUID.randomUUID();