diff --git a/src/main/java/org/operaton/fitpub/model/entity/Follow.java b/src/main/java/org/operaton/fitpub/model/entity/Follow.java index 39437fa..5c6c06c 100644 --- a/src/main/java/org/operaton/fitpub/model/entity/Follow.java +++ b/src/main/java/org/operaton/fitpub/model/entity/Follow.java @@ -31,13 +31,22 @@ public class Follow { /** * The local user who is following. + * NULL if this is a remote user following a local user. */ - @Column(name = "follower_id", nullable = false) + @Column(name = "follower_id") private UUID followerId; /** - * The ActivityPub actor URI being followed (local or remote). + * The remote actor URI of the follower (for remote-to-local follows). * Example: https://mastodon.social/users/alice + * NULL if followerId is set (local follower). + */ + @Column(name = "remote_actor_uri", length = 512) + private String remoteActorUri; + + /** + * The ActivityPub actor URI being followed (local or remote). + * Example: https://example.com/users/bob */ @Column(name = "following_actor_uri", nullable = false, length = 512) private String followingActorUri; diff --git a/src/main/java/org/operaton/fitpub/service/FederationService.java b/src/main/java/org/operaton/fitpub/service/FederationService.java index cac59d7..b1945e4 100644 --- a/src/main/java/org/operaton/fitpub/service/FederationService.java +++ b/src/main/java/org/operaton/fitpub/service/FederationService.java @@ -128,7 +128,14 @@ public class FederationService { @Transactional public void sendAcceptActivity(Follow follow, User localUser) { try { - RemoteActor remoteActor = fetchRemoteActor(follow.getFollowingActorUri()); + // Get the remote actor who sent the follow request + String remoteActorUri = follow.getRemoteActorUri(); + if (remoteActorUri == null) { + log.error("Cannot send Accept: Follow has no remote actor URI"); + return; + } + + RemoteActor remoteActor = fetchRemoteActor(remoteActorUri); String acceptId = baseUrl + "/activities/" + UUID.randomUUID(); String actorUri = baseUrl + "/users/" + localUser.getUsername(); diff --git a/src/main/java/org/operaton/fitpub/service/InboxProcessor.java b/src/main/java/org/operaton/fitpub/service/InboxProcessor.java index ba43158..dafb5b8 100644 --- a/src/main/java/org/operaton/fitpub/service/InboxProcessor.java +++ b/src/main/java/org/operaton/fitpub/service/InboxProcessor.java @@ -95,6 +95,7 @@ public class InboxProcessor { // Note: We're storing it from the perspective of "who is following whom" Follow follow = Follow.builder() .followerId(null) // Remote actor, so no local user ID + .remoteActorUri(actor) // The remote actor who is following .followingActorUri(expectedObjectUri) // The local user being followed .status(Follow.FollowStatus.ACCEPTED) // Auto-accept for now .activityId(activityId) diff --git a/src/main/resources/db/migration/V8__add_remote_actor_uri_to_follows.sql b/src/main/resources/db/migration/V8__add_remote_actor_uri_to_follows.sql new file mode 100644 index 0000000..f7787cf --- /dev/null +++ b/src/main/resources/db/migration/V8__add_remote_actor_uri_to_follows.sql @@ -0,0 +1,19 @@ +-- Migration V8: Add remote_actor_uri column to follows table + +-- Add the column +ALTER TABLE follows ADD COLUMN remote_actor_uri VARCHAR(512); + +-- Add index for remote_actor_uri +CREATE INDEX idx_follows_remote_actor_uri ON follows(remote_actor_uri) WHERE remote_actor_uri IS NOT NULL; + +-- Update the constraint to allow either follower_id OR remote_actor_uri +ALTER TABLE follows ALTER COLUMN follower_id DROP NOT NULL; + +-- Add check constraint to ensure either follower_id or remote_actor_uri is set (but not both) +ALTER TABLE follows ADD CONSTRAINT chk_follows_actor CHECK ( + (follower_id IS NOT NULL AND remote_actor_uri IS NULL) OR + (follower_id IS NULL AND remote_actor_uri IS NOT NULL) +); + +-- Add comment for documentation +COMMENT ON COLUMN follows.remote_actor_uri IS 'Remote ActivityPub actor URI for remote-to-local follows (null if local follower)';